SQL: подготовленные выражения, транзакции, Lua-интероп

Рейтинг: 40.9% · 8 голосов
Исчерпывающий курс по Tarantool 3.x: модель данных, движки memtx и vinyl, Lua и файберы, транзакции и MVCC, SQL, конфигурация (box.cfg и декларативная 3.x), репликация и Raft, шардирование vshard, эксплуатация, безопасность. 47 уроков со схемами.
Ответить
Аватара пользователя
denis_tnt
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

SQL: подготовленные выражения, транзакции, Lua-интероп

Сообщение denis_tnt »

Оглавление курса (47)
  1. Что такое Tarantool: in-memory СУБД и сервер приложений
  2. Архитектура изнутри: процесс, потоки, event-loop
  3. Установка и первый запуск: tt CLI, пакеты, Docker
  4. Интерактив: консоль, admin-консоль, первые команды
  5. Спейсы и кортежи: форматы, типы данных
  6. Типы индексов и их применимость
  7. Движки хранения: memtx vs vinyl
  8. DDL: схема, создание спейсов и индексов, миграции
  9. DML и выборки: insert/update/upsert, итераторы
  10. Персистентность: WAL, снапшоты, recovery
  11. Внутренности memtx: аллокаторы slab/arena, память
  12. Внутренности vinyl: LSM, компакция, тюнинг
  13. Lua и LuaJIT в Tarantool: box, модули, rocks
  14. Файберы: кооперативная многозадачность, каналы
  15. Транзакции: ACID, изоляция, MVCC
  16. Хранимые процедуры, модули, организация приложения
  17. net.box: удалённые вызовы, async
  18. Пулы соединений, балансировка, реконнект
  19. Ошибки и диагностика: box.error, pcall
  20. Типы и сериализация: MsgPack, decimal, datetime, uuid
  21. SQL в Tarantool: возможности и связь с box
  22. SQL: таблицы, JOIN, подзапросы, представления
  23. SQL: подготовленные выражения, транзакции, Lua-интероп (вы здесь)
  24. Классическая конфигурация box.cfg (legacy 1.x/2.x)
  25. Декларативная конфигурация 3.x: config.yaml, иерархия
  26. Роли и приложения в 3.x
  27. Централизованная конфигурация: etcd / config storage
  28. tt CLI глубоко: разработка, сборка, запуск
  29. Cartridge (официальный legacy) и миграция на 3.x
  30. Репликация: replicaset, топологии
  31. Механика репликации: WAL-стриминг, vclock
  32. Синхронная репликация и выборы лидера (Raft)
  33. Жизненный цикл узла: bootstrap, join, rejoin
  34. vshard: router/storage, виртуальные бакеты
  35. Решардинг и rebalancing бакетов
  36. Запросы поверх шардов: map-reduce, crud
  37. Мониторинг: метрики, Prometheus, Grafana
  38. Логирование и аудит
  39. Бэкапы и восстановление
  40. Безопасность: аутентификация, RBAC, TLS
  41. Производительность: профилирование, тюнинг
  42. Обновления: схема, rolling upgrade
  43. Деплой в продакшен: Docker, топология (официальные паттерны)
  44. Администрирование через официальный TCM (Tarantool Cluster Manager)
  45. Коннекторы: Python, Go, Java
  46. Ключевые модули (rocks): crud, metrics, queue, expirationd
  47. Capstone: шардированный отказоустойчивый кластер
Краткий обзор

В Tarantool SQL и NoSQL (box) - это не два разных движка, а две двери в одно и то же хранилище. Таблица, созданная через CREATE TABLE, физически живет как обычный space с tree-индексом; SQL-выражение в итоге превращается в те же низкоуровневые операции над спейсами, что и box.space:insert. Этот урок про шов между мирами: как байт-код подготовленного выражения кэшируется и переиспользуется, как SQL-транзакция отображается на транзакционный движок box, и как код перетекает из SQL в Lua и обратно.
Главная идея урока: SQL в Tarantool - это тонкий слой над тем же транзакционным и индексным движком, что и box. Понимая, во что транслируется SQL, вы перестаете воспринимать prepared statements и транзакции как магию.
Механика: как устроено внутри

Путь SQL-запроса. Когда вы вызываете box.execute([[SELECT ...]]), строка проходит парсер, превращается в дерево, а затем компилируется в байт-код виртуальной машины (VDBE - наследие SQLite, на котором исторически построен SQL-фронтенд Tarantool). Этот байт-код исполняется опкод за опкодом, а операции чтения/записи обращаются к тем же memtx/vinyl спейсам, что и NoSQL. Компиляция стоит заметного времени, поэтому для часто повторяемых запросов ее имеет смысл выполнить один раз.

Подготовленные выражения. box.prepare(sql) делает именно это: компилирует строку в байт-код и кладет его в кэш. Возвращается prepared_table со следующими полями:
  • stmt_id - идентификатор, это хэш от текста запроса (важное следствие ниже);
  • execute - функция исполнения;
  • unprepare - функция освобождения;
  • params, param_count - описание плейсхолдеров;
  • metadata - присутствует только для SELECT и PRAGMA (описание колонок результата).
Кэш байт-кода общий (shared) для всех сессий инстанса, но исполнить чужой подготовленный statement нельзя: сессия X не может выполнить то, что подготовила сессия Y, хотя байт-код лежит в общем кэше. Поскольку stmt_id - это хэш текста, два одинаковых по тексту prepare дадут один и тот же stmt_id и переиспользуют скомпилированный байт-код.

Транзакции. SQL-операторы START TRANSACTION / COMMIT / ROLLBACK / SAVEPOINT / RELEASE SAVEPOINT - это синонимы box.begin / box.commit / box.rollback / box.savepoint / box.rollback_to_savepoint. Они управляют одним и тем же транзакционным контекстом файбера. Вне явной транзакции каждый box.execute с изменением данных авто-коммитится. Ограничения наследуются от движка: в memtx yield (переключение файбера, в том числе сетевой вызов) внутри активной транзакции откатывает ее; в vinyl yield разрешен. Поэтому держать транзакцию надо коротко.

Изображение
Взаимодействие SQL-фронтенда, box и Lua

Ключевые команды и код

Подготовка, плейсхолдеры, исполнение. Плейсхолдер - это либо знак вопроса (позиционный), либо двоеточие с именем (именованный). Параметры передаются вторым аргументом - таблицей.

Код: Выделить всё

box.cfg{}
box.execute([[CREATE TABLE users (id INTEGER PRIMARY KEY, name STRING);]])

-- позиционные плейсхолдеры
local p = box.prepare([[INSERT INTO users VALUES (?, ?);]])
p:execute({1, 'Alice'})
p:execute({2, 'Bob'})

-- именованные плейсхолдеры
local q = box.prepare([[SELECT name FROM users WHERE id = :uid;]])
local res = q:execute({{[':uid'] = 1}})
-- res.metadata - описание колонок, res.rows - данные

p:unprepare()   -- то же, что box.unprepare(p.stmt_id), аналог DEALLOCATE PREPARE
q:unprepare()
Эквивалентные формы: p:execute(...) и box.execute(p.stmt_id, ...) делают одно и то же; p:unprepare() и box.unprepare(p.stmt_id) - тоже.

Транзакция средствами SQL и box.

Код: Выделить всё

-- стиль SQL
box.execute([[START TRANSACTION;]])
box.execute([[INSERT INTO users VALUES (3, 'Carol');]])
box.execute([[SAVEPOINT s1;]])
box.execute([[INSERT INTO users VALUES (4, 'Dave');]])
box.execute([[ROLLBACK TO SAVEPOINT s1;]])  -- откатит только Dave
box.execute([[COMMIT;]])

-- эквивалент через box (тот же контекст)
box.begin()
box.execute([[INSERT INTO users VALUES (5, 'Eve');]])
box.commit()
Вызов Lua из SQL. Это аналог хранимых процедур, но на Lua, а не на SQL/PSM. Функцию регистрируют через box.schema.func.create с exports = {'LUA','SQL'}; она становится персистентной (переживает рестарт).

Код: Выделить всё

box.schema.func.create('ADD_TAX', {
  language = 'LUA',
  returns = 'double',
  param_list = {'double'},
  exports = {'LUA', 'SQL'},
  is_deterministic = true,
  body = [[function(price) return price * 1.2 end]]})

box.execute([[SELECT ADD_TAX(100.0);]])
-- быстрый одноразовый код без функции:
box.execute([[SELECT lua('return os.time()');]])
Частые заблуждения и грабли
  • Prepared statement живет вечно. Нет. Любой DDL (CREATE/DROP/ALTER любого объекта) инвалидирует ВСЕ подготовленные выражения инстанса - даже если этот объект в запросе не упоминается, даже если DDL был откатан, даже если его сделала другая сессия. После такого execute по старому stmt_id вернет ошибку - надо готовить заново.
  • Регистр идентификаторов. SQL приводит обычные (без кавычек) идентификаторы к ВЕРХНЕМУ регистру. CREATE TABLE things создает space THINGS, и из Lua он виден как box.space.THINGS. Функция _decode из SQL вызовется как _DECODE. Чтобы сохранить регистр, нужны "кавычки".
  • Lua nil, box.NULL и SQL NULL - одно и то же. lua('return box.NULL') IS NOT NULL вернет FALSE.
  • Рекурсивный доступ к спейсу. Если SQL-запрос вызывает Lua-функцию (или lua()), которая трогает space, лежащий под таблицей этого же запроса, можно получить зависание или бесконечный цикл. Не пишите SELECT f() FROM test, если f() сама пишет в TEST.
  • Yield в memtx-транзакции. Сетевой вызов, явный fiber.yield или долгая операция внутри START TRANSACTION на memtx откатят транзакцию. Держите транзакции короткими и без внешних обращений.
  • Конкатенация вместо плейсхолдеров. Склеивать значения в строку запроса (как в примере million-row insert из доков) - медленно и опасно (инъекции, нет переиспользования байт-кода). Используйте плейсхолдеры и/или prepare.
  • Чужой stmt_id. Кэш общий, но stmt_id, подготовленный в другой сессии, в вашей не исполнится.
Мини-лаба
  • Создайте таблицу goods (id INTEGER PRIMARY KEY, price DOUBLE). Подготовьте через box.prepare INSERT с именованным плейсхолдером :p, вставьте 3 строки одним и тем же prepared-объектом. Затем зарегистрируйте Lua-функцию WITH_VAT(price) -> price*1.2 (exports LUA+SQL) и выполните SELECT id, WITH_VAT(price) FROM goods. После этого выполните любой DDL (например CREATE TABLE tmp(id INTEGER PRIMARY KEY)) и попробуйте снова p:execute - убедитесь, что prepared-выражение инвалидировалось, и пересоздайте его.
Контрольные вопросы
  • Что физически делает box.prepare и почему он ускоряет повторное исполнение? Из чего складывается stmt_id?
  • Какие события инвалидируют все подготовленные выражения инстанса и почему это происходит даже для не упомянутого в запросе объекта?
  • Почему START TRANSACTION и box.begin - это одно и то же, и что произойдет при yield внутри транзакции на движке memtx против vinyl?
  • В чем разница между регистрацией функции через box.schema.func.create с exports={'LUA','SQL'} и вызовом lua('return ...') прямо в SELECT? Когда что уместно?
👍4 ❤️4 🔥2 😄 🤔
Ответить
← Предыдущая глава
SQL: таблицы, JOIN, подзапросы, представления
Следующая глава →
Классическая конфигурация box.cfg (legacy 1.x/2.x)

Все главы курса «Tarantool: in-memory СУБД и сервер приложений с нуля до продакшена»

Поделиться темой: ✈ Telegram VK
  • Похожие темы

Вернуться в «Tarantool: СУБД и сервер приложений»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость