Транзакции и запросы к YDB
Этот раздел описывает особенности реализации YQL для YDB транзакций.
Язык запросов
Основным средством создания, модификации и управления данными в YDB является декларативный язык запросов YQL. YQL — это диалект SQL, который может считаться стандартом для общения с базами данных. Кроме того, YDB поддерживает набор специальных RPC, например, для работы с древовидной схемой или для управления кластером.
Режимы транзакций
YDB поддерживает несколько режимов выполнения транзакций. По умолчанию транзакции выполняются в режиме Serializable, который предоставляет самый строгий уровень изоляции для пользовательских транзакций. Режим выполнения транзакции задаётся в настройках при её создании. Примеры для YDB SDK см. в Установка режима выполнения транзакции. Во YDB UI режим транзакции также можно выбрать в настройках.
Поддерживаются режимы чтения-записи: Serializable (по умолчанию) и Snapshot Read-Write; режимы только для чтения: Snapshot Read-Only и Stale Read-Only. Режим Online Read-Only оставлен для совместимости со старым кодом (legacy); в новых сценариях вместо него следует использовать Snapshot Read-Only.
Serializable
Суть. Сериализуемое исполнение пользовательских транзакций (Serializable уровень изоляции).
Особенности. Используется механизм Optimistic Concurrency Control. На прочитанные в ходе выполнения транзакции строки ставятся оптимистичные блокировки. При завершении транзакции проверяется, что блокировки не были инвалидированы. Оптимистичность блокировок выливается в важное для пользователя свойство — в случае конфликта выигрывает транзакция, которая завершается первой. Конкурирующие транзакции завершатся с ошибкой Transaction locks invalidated.
Гарантии.
- Результат успешно выполненных параллельных транзакций эквивалентен некоторому последовательному порядку их выполнения;
- Для успешных транзакций нет аномалий чтений;
- Транзакция видит все изменения, которые были зафиксированы (закоммичены) до её первого чтения (времени взятия снапшота), плюс собственные изменения, сделанные ранее в этой же транзакции (read-your-own-writes);
- Гарантируется линеаризуемость по ключу: если транзакция T1, затрагивающая некоторый ключ, завершилась до начала транзакции T2, затрагивающей тот же ключ, то в порядке выполнения T1 будет находиться перед T2. Для транзакций, работающих с разными ключами, такой порядок не гарантируется — наблюдаемый порядок их фиксации может отличаться от реального порядка их завершения во времени.
Snapshot Read-Write
Суть. Представляет собой изоляцию Snapshot Isolation (аналог Repeatable Read в PostgreSQL).
Чтения производятся из согласованного снимка данных, зафиксированного до момента выполнения первого чтения. Транзакция успешно зафиксируется только при условии, что от момента взятия снимка данных до момента коммита транзакции модифицированные ей строки не были модифицированы другими транзакциями.
Особенности. Используется механизм Optimistic Concurrency Control. В отличие от Serializable, транзакции не берут блокировки на читаемые строки — это позволяет им успешно зафиксироваться, даже если прочитанные ими данные были модифицированы другими транзакциями. На модифицируемые же строки берутся блокировки: при параллельных транзакциях, изменяющих те же ключи, только транзакция, которая завершается первой, завершится успешно. Конкурирующие транзакции будут отклонены с write-write конфликтом на этапе фиксации с ошибкой Transaction locks invalidated.
Гарантии.
- Все чтения в транзакции видят одно и то же состояние данных на снимке, полученном до момента выполнения первого чтения, плюс собственные изменения, сделанные ранее в этой же транзакции (read-your-own-writes);
- При наличии write-write конфликта транзакция не сможет зафиксироваться;
- Может наблюдаться аномалия write skew.
Snapshot Read-Only
Суть. Транзакция работает с целостным снимком базы, зафиксированным до момента выполнения первого чтения. Гарантии на чтение данных такие же, как Snapshot Read-Write. Запись в данном режиме запрещена, за счет этого режим эффективнее, чем Snapshot Read-Write, когда необходимо только чтение данных.
Особенности. Обеспечивает максимальную актуальность данных на момент начала транзакции, но может иметь более высокую задержку ответа из‑за необходимости формирования снимка.
Гарантии. Все чтения в транзакции видят одно и то же состояние данных на снимке; коммиты после момента взятия снапшота не видны.
Stale Read-Only
Суть. Чтения выполняются на репликах таблетки (шарда) с возможным отставанием от лидера таблетки (шарда) (обычно доли секунды). Режим хорошо подходит для сценария чтения по ключу, когда нужны минимальные задержки. Пользователь читает закоммиченные, но возможно устаревшие данные.
Особенности. Низкая задержка и высокая пропускная способность за счёт чтения с реплик. Реплика для чтения выбирается обычно в той же зоне доступности, но точное расположение не гарантируется.
Гарантии. Согласованность данных на уровне ключа внутри одного SELECT выражения; между разными SELECT выражениями в одной транзакции согласованность не гарантируется.
Ограничения. Нет единого снимка на всю транзакцию; возможна задержка относительно данных на лидере (данные могут быть не самыми свежими). Использование реплик для чтений возможно только в случае, если все читаемые строки находятся в одном шарде. Если же чтение затрагивает несколько шардов, то чтение будет произведено из лидеров со взятием снимка аналогично Snapshot Read-Only. Чтение из колоночных таблиц в этом режиме не поддерживается (см. предупреждение ниже). Не поддерживаются интерактивные транзакции. Дополнительные ограничения и нюансы по типам запросов см. в документации SDK для выбранного языка.
Online Read-Only
Устаревший (legacy) режим, сохранённый для совместимости. Для новых приложений при чтении без записи используйте Snapshot Read-Only. Подробности и примеры вызова в SDK по-прежнему можно найти в Online Read-Only.
Ограничение для Online Read-Only и Stale Read-Only
Эти режимы не поддерживают чтение из колоночных таблиц. Попытка чтения вызовет ошибку следующего вида:
Read from column tables is not supported in Online Read-Only or Stale Read-Only transaction modes. Use Serializable or Snapshot Read-Only mode instead.
Для транзакций с чтением из колоночных таблиц используйте:
- Serializable — режим по умолчанию;
- Snapshot Read-Only — режим чтения из согласованного снимка.
Неявные транзакции
Логика неявных транзакций применяется при отправке одного YQL-скрипта на сервер без явного выбора режима транзакции. Типичные точки входа:
- YDB UI — вкладка Query на странице базы данных (форма выполнения запроса), при запуске без выбора режима транзакции в настройках.
- YDB CLI — разовая отправка скрипта командой
ydb sql. - Приложения на YDB SDK — режим ImplicitTx.
Если для запроса не задан режим транзакции, YDB автоматически управляет его поведением. Такой режим называется неявной транзакцией.
В этом режиме YDB на основе запроса определяет, выполнить его вне транзакции или обернуть в транзакцию с режимом Serializable. Режим неявной транзакции является универсальным для выполнения запроса, так как поддерживает инструкции любого вида с определённым поведением, описанным ниже.
Поведение для разных видов инструкций
-
Инструкции Data Definition Language (DDL)
DDL-инструкции (такие как CREATE TABLE, DROP TABLE и т.д.) выполняются вне транзакции. Запрос может состоять только из DDL-инструкций. При возникновении ошибки изменения, внесённые предыдущими инструкциями запроса, не откатываются. -
Инструкции Data Manipulation Language (DML)
DML-инструкции (такие как UPSERT, SELECT, UPDATE и т.д.) оборачиваются в транзакцию с режимом Serializable. Запрос может состоять только из DML-инструкций. При успешном выполнении изменения коммитятся (фиксируются), а при возникновении ошибки — откатываются. -
Инструкции пакетного изменения
Инструкции пакетного изменения (такие как BATCH UPDATE и BATCH DELETE FROM) выполняются вне транзакции. Запрос может состоять только из одной инструкции пакетного изменения. При возникновении ошибки изменения инструкции не откатываются.
Сводная таблица
| Тип инструкции | Обработка неявной транзакции | Поддержка нескольких инструкций | Откат при ошибке |
|---|---|---|---|
| DDL | Вне транзакции | Да (только DDL) | Нет |
| DML | Автоматическая транзакция (Serializable) | Да (только DML) | Да |
| Инструкции пакетного изменения | Вне транзакции | Нет | Нет |
Чтобы явно задать режим транзакции, используйте соответствующие настройки в каждой точке входа:
- YDB UI — выберите режим транзакции в настройках выполнения на вкладке Query.
- YDB CLI — у подкоманды
table query executeдля запросов типаdataзадайте параметр--tx-mode(по умолчаниюserializable-rw, что соответствует режиму Serializable). - YDB SDK — см. установку режима в YDB SDK.
Язык YQL
Реализованные конструкции YQL можно разделить на два класса: data definition language (DDL) и data manipulation language (DML).
Подробнее о поддерживаемых конструкциях YQL можно почитать в документации YQL.
Ниже перечислены возможности и ограничения поддержки YQL в YDB, на которые стоит обратить внимание:
- Допускаются multistatement transactions, то есть транзакции, состоящие из последовательности выражений YQL. При выполнении транзакции допускается взаимодействие с клиентской программой, иначе говоря, взаимодействие клиента с базой может выглядеть следующим образом:
начать транзакцию и выполнить SELECT; проанализировать результаты SELECT на клиенте; ...; выполнить UPDATE и коммит транзакции. Каждый из запросов внутри транзакции может также содержать несколько выражений YQL. Стоит отметить, что если тело транзакции полностью сформировано до обращения к базе данных, то транзакция может обрабатываться эффективнее; - В YDB не поддерживается возможность смешивать DDL и DML запросы в одной транзакции. Традиционное понятие ACID транзакции применимо именно к DML запросам, то есть к запросам, которые меняют данные. DDL запросы должны быть идемпотентными, то есть повторяемы в случае ошибки. Если необходимо выполнить действие со схемой, то каждое из действий будет транзакционно, а набор действий — нет;
- Любые ошибки инвалидируют всю транзакцию целиком, а не отдельный запрос, поэтому после завершения транзакции с ошибкой, сообщающей о временном сбое, выполнение транзакции необходимо повторять с самого начала;
- Чтения в транзакции видят все изменения данных, которые были выполнены в той же транзакции ранее;
- Все изменения, производимые в рамках транзакции, накапливаются в памяти сервера базы данных. Они не видны другим транзакциям до момента успешного завершения текущей и применяются атомарно при коммите. Описанная схема накладывает ограничение: объём изменений в рамках одной транзакции должен умещаться в оперативную память. Деталь реализации: если транзакция читает данные из таблицы, которую она ранее модифицировала, накопленные изменения записываются в шарды досрочно — это влияет на эффективность (см. рекомендацию ниже). Досрочно записанные данные не видны другим транзакциям и откатываются при отмене транзакции;
- Для эффективности транзакций избегайте чтения из таблиц, ранее модифицированных в этой же транзакции (read-after-write), так как это приводит к преждевременной записи данных в шарды. Для каждой таблицы выполняйте все чтения раньше модификаций.
Подробнее о поддержке YQL в YDB можно прочитать в документации YQL.
Распределенные транзакции
Таблица в YDB может быть шардирована по диапазонам значений первичного ключа. Различные шарды таблицы могут обслуживаться разными серверами распределенной БД (в том числе расположенными в разных локациях), а также могут независимо друг от друга перемещаться между серверами для перебалансировки или поддержания работоспособности шарда при отказах серверов или сетевого оборудования.
Топик в YDB может быть шардирован на несколько партиций. Различные партиции топика, как и шарды таблицы, могут обслуживаться разными серверами распределенной БД.
В YDB поддерживаются распределенные транзакции. Распределенные транзакции — это транзакции, которые затрагивают более одного шарда одной или нескольких таблиц и топиков. Они требуют больше ресурсов и выполняются дольше. В то время как точечные чтения и записи могут выполняться за время до 10 мс в 99 перцентиле, распределенные транзакции, как правило, занимают от 20 до 500 мс.
Транзакции с участием топиков и таблиц
Важно
Поддерживается только для строковых таблиц. Поддержка функциональности для колоночных таблиц находится в разработке.
YDB поддерживает транзакции с участием строковых таблиц и/или топиков. Таким образом, можно транзакционно перекладывать данные из таблиц в топики и в обратном направлении, а также между топиками, чтобы данные не терялись и не дублировались даже в случае непредвиденных обстоятельств.
Подробнее о транзакционных операциях при работе с топиками см. в Транзакции с участием топиков и Работа с топиками.
Транзакции с участием строковых и колоночных таблиц
В настоящее время одновременное использование колоночных и строковых таблиц поддерживается в транзакциях, в которых данные только читаются, но не изменяются. Поддержка транзакций с возможностью модификации данных при одновременном использовании строковых и колоночных таблиц находится в разработке.
Если попытаться выполнить операцию записи в транзакции, в которой задействованы и колоночные, и строковые таблицы, транзакция завершится с ошибкой: Write transactions that use both row-oriented and column-oriented tables are disabled at current time.