SQL в Tarantool: возможности и связь с box

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

SQL в Tarantool: возможности и связь с box

Сообщение 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 как в NoSQL-хранилище: спейсы, таплы, box.space:select. Но внутри того же процесса живёт полноценный SQL-движок. SQL появился в Tarantool 2.1 и с тех пор имеет статус release: есть JOIN-ы, подзапросы, VIEW, триггеры, ограничения (constraints), транзакции и совместимость с большей частью обязательных требований стандарта SQL:2016.

Ключевая идея урока: SQL в Tarantool - это не отдельная база данных и не отдельный движок хранения. Это транслятор поверх того же box-движка. Команда CREATE TABLE создаёт обычный спейс, строка превращается в тапл, а SELECT под капотом дёргает те же индексы memtx или vinyl, что и box.space:select. SQL и NoSQL смотрят на одни и те же данные с двух сторон.
База данных Tarantool SQL и база данных Tarantool NoSQL - это одно и то же. Но часть операций доступна только в SQL, а часть - только в NoSQL. Смешивать SQL-запросы и NoSQL-вызовы в одной программе разрешено.
Механика: как SQL ложится на box

Точка входа в SQL - Lua-функция box.execute(string). Никакого отдельного сетевого порта или процесса у SQL нет: вы исполняете SQL прямо в Lua-сессии или через коннектор по бинарному протоколу iproto. Что происходит, когда вы вызываете box.execute:
  • Парсер разбирает текст SQL в дерево разбора.
  • Планировщик строит план: какие спейсы читать, какие индексы использовать, в каком порядке делать JOIN.
  • План компилируется в байт-код виртуальной машины (наследие SQLite-движка, на котором построен SQL Tarantool).
  • Эта VM исполняет байт-код, но вместо собственного движка хранения вызывает публичный API box: чтение по индексу, вставку таплов, открытие транзакции.
То есть слой хранения у SQL и NoSQL общий. Спейс, индекс, таплы, WAL, snapshot, репликация - всё одно и то же. SQL лишь добавляет фронтенд: грамматику, типы, планировщик и проверку целостности.

Самое наглядное доказательство общего слоя - имена. Создайте таблицу:

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

box.cfg{}
box.execute([[CREATE TABLE things (id INTEGER PRIMARY KEY, remark STRING);]])
box.execute([[INSERT INTO things VALUES (55, 'Hello SQL world!');]])
Теперь та же таблица видна как обычный спейс NoSQL - под именем в ВЕРХНЕМ регистре:

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

tarantool> box.space.THINGS
---
- engine: memtx
  field_count: 2
  index:
    0:
      type: TREE
      name: pk_unnamed_THINGS_1
      parts: [{type: integer, fieldno: 1, ...}]
  name: THINGS
  id: 520
И наоборот: вставка через NoSQL немедленно видна в SQL.

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

box.space.THINGS:insert{56, 'from NoSQL'}
box.execute([[SELECT * FROM things WHERE id > 0;]])
Обратите внимание на регистр: SQL без двойных кавычек приводит идентификаторы к ВЕРХНЕМУ регистру, поэтому в NoSQL спейс называется THINGS, а не things. Системные таблицы (_space, _index, _trigger) наоборот в нижнем регистре, и в SQL их нужно брать в "кавычки".

Изображение
SQL-фронтенд транслируется в общий API box

Результат box.execute

Для SELECT и PRAGMA box.execute возвращает таблицу с двумя полями: metadata (имена колонок и их NoSQL-типы) и rows (содержимое строк). Для INSERT/UPDATE/DELETE возвращается row_count. Важно: типы в metadata - это типы Tarantool/NoSQL (integer, string, scalar, double, decimal), а не абстрактные SQL-типы. Это ещё одно напоминание, что под SQL лежит box.

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

tarantool> box.execute([[VALUES ('hello');]])
---
- metadata:
  - name: COLUMN_1
    type: string
  rows:
  - ['hello']
...
Подготовленные запросы

Чтобы не парсить и не планировать один и тот же запрос много раз, используйте box.prepare(). Он один раз компилирует байт-код и возвращает stmt_id, после чего вы исполняете запрос с разными параметрами через :execute. Параметры подставляются плейсхолдерами ? или :name - это и быстрее, и защищает от SQL-инъекций.

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

local p = box.prepare([[SELECT * FROM things WHERE id = ?;]])
p:execute({55})
p:execute({56})
p:unprepare()
EXPLAIN

EXPLAIN показывает тот самый байт-код VM, а EXPLAIN QUERY PLAN - выбранные индексы и порядок обхода. Это главный инструмент, чтобы убедиться, что планировщик использует ваш TREE-индекс, а не сканирует весь спейс.

Частые заблуждения и грабли
  • "SQL - это отдельный движок, он медленнее". Нет. Слой хранения общий. SQL добавляет накладные расходы на парсинг и планирование, но сами чтения/записи идут через тот же box. Для повторяющихся запросов эти расходы убирает box.prepare.
  • Регистр имён. CREATE TABLE things даёт спейс THINGS. Если потом искать box.space.things - получите nil. Имена без кавычек всегда в верхнем регистре.
  • Не все спейсы видны в SQL. Поля NoSQL-спейса доступны в SQL, только если они скалярные и описаны во format(). Индекс используется планировщиком SQL, только если это TREE-индекс. HASH/BITSET/RTREE для SQL невидимы, а MAP- и ARRAY-поля при SELECT * могут давать ошибку.
  • Целостность можно обойти снизу. Foreign key и CHECK, заданные в SQL, проверяет SQL-фронтенд. Если писать в спейс напрямую через box.space:insert, эти ограничения не проверяются - NoSQL-вызов может нарушить FK.
  • SQL-триггеры не срабатывают на NoSQL-операции. Триггер, созданный через CREATE TRIGGER, отрабатывает только при SQL-запросах. Запись через box.space:replace его не запустит (для этого есть отдельные NoSQL-триггеры on_replace).
  • Часть DDL только через NoSQL. Нестандартные опции индекса (тип HASH, особый id, последовательности) задаются box.space...:create_index, а не SQL. Проверка типов в SQL мягче, чем в классических СУБД.
Мини-лаба

Цель: увидеть, что SQL и box - один слой хранения.
  • Запустите tarantool, выполните box.cfg{}.
  • Создайте таблицу: box.execute([[CREATE TABLE users (id INTEGER PRIMARY KEY, name STRING);]]).
  • Вставьте строку через SQL: box.execute([[INSERT INTO users VALUES (1, 'Ann');]]).
  • Посмотрите спейс глазами NoSQL: выведите box.space.USERS и его :format(). Найдите движок (engine) и тип индекса.
  • Вставьте строку через NoSQL: box.space.USERS:insert{2, 'Bob'}.
  • Прочитайте обе строки через SQL: box.execute([[SELECT * FROM users ORDER BY id;]]) - убедитесь, что видны и Ann, и Bob.
  • Запустите box.execute([[EXPLAIN QUERY PLAN SELECT * FROM users WHERE id = 1;]]) и найдите, что используется первичный TREE-индекс.
Ожидаемый вывод: данные, вставленные обоими способами, видны с обеих сторон, а спейс USERS - обычный memtx-спейс с TREE-индексом.

Контрольные вопросы
  • Что физически создаёт команда CREATE TABLE в терминах box: новый движок, новый процесс или обычный спейс? Под каким именем таблица things будет видна в box.space?
  • Через какую Lua-функцию исполняется SQL и что лежит в полях metadata и rows ответа на SELECT? Почему типы в metadata - это типы NoSQL, а не SQL?
  • Почему запись через box.space:insert может нарушить foreign key, заданный в SQL, и почему SQL-триггер не сработает на такую запись?
  • При каких условиях поле и индекс NoSQL-спейса будут доступны планировщику SQL? Что даёт box.prepare по сравнению с обычным box.execute?
👍1 ❤️2 🔥1 😄 🤔
Ответить
← Предыдущая глава
Типы и сериализация: MsgPack, decimal, datetime, uuid
Следующая глава →
SQL: таблицы, JOIN, подзапросы, представления

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

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

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

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

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