Tarantool CE/EE Documentation portal logo
Помощь
Обновлена 15 сентября 2026 г. в 08:55

Режим транзакций: MVCC

Начиная с версии 2.6.1, в Tarantool доступен другой режим поведения транзакций, который допускает передачу управления ("yields") внутри memtx-транзакций. Этот процесс управляется менеджером транзакций.

Такой режим допускает параллельное выполнение транзакций, но может приводить к конфликтам. Его можно использовать с движком хранения memtx. Движок хранения vinyl также поддерживает режим MVCC, но имеет другую реализацию.

Менеджер транзакций

Менеджер транзакций предназначен для изоляции параллельных транзакций и обеспечивает serializable уровень изоляции транзакций. Он состоит из двух частей:

  • MVCC – механизм управления параллельным доступом с поддержкой нескольких версий, который хранит все действия по изменению всех транзакций. Он также создает транзакционное представление состояния базы данных и представление чтения (фиксированное состояние базы данных, которое никогда не изменяется другими транзакциями) при необходимости.

  • Conflict manager – менеджер конфликтов, который отслеживает изменения транзакций и определяет их корректность в порядке сериализации. Менеджер конфликтов объявляет транзакции конфликтующими или отправляет транзакции в представления чтения при необходимости.

    Начиная с версии 2.10.1, менеджер конфликтов обнаруживает конфликты сразу после фиксации первой из нескольких конфликтующих транзакций. С этого момента любые операции CRUD в конфликтующей транзакции приводят к ошибкам до тех пор, пока транзакция не будет откащена.

Менеджер транзакций также обеспечивает неклассический уровень изоляции snapshot. Снимок здесь не обязательно привязан ко времени начала транзакции, как в классическом подходе, когда транзакция получает согласованное состояние базы данных. Менеджер конфликтов определяет, получает ли каждая транзакция снимок и когда именно. Это позволяет избежать некоторых конфликтов по сравнению с классическим подходом изоляции снимков.

Включение менеджера транзакций

По умолчанию менеджер транзакций отключен. Для его включения через box.cfg используйте параметр memtx_use_mvcc_engine.

box.cfg{memtx_use_mvcc_engine = true}

Настройка уровня изоляции транзакций

Для уровня изоляции транзакций менеджер транзакций поддерживает следующие значения:

  • best-effort (по умолчанию)
  • read-committed
  • read-confirmed
  • linearizable (только для конкретной транзакции)

Использование best-effort в качестве значения по умолчанию позволяет MVCC рассматривать действия транзакций независимо и определять для них наилучший уровень изоляции. Это повышает вероятность успешного завершения транзакции и помогает избежать возможных конфликтов.

Чтобы задать другой уровень изоляции по умолчанию, например read-committed, используйте следующую команду:

box.cfg { txn_isolation = 'read-committed' }

Учтите, что уровень изоляции linearizable нельзя задать по умолчанию — он доступен только для конкретной транзакции. Задать уровень изоляции для конкретной транзакции можно в вызове box.begin():

box.begin({ txn_isolation = 'best-effort' })

В этом случае также можно использовать значение default. Оно устанавливает уровень изоляции транзакции в значение, заданное в box.cfg.

Уровень изоляции также можно задать в методе net.box stream:begin() и в запросе бинарного протокола IPROTO_BEGIN.

Выбор подходящего значения зависит от наличия конфликтов. Если конфликтов много, следует задать другое значение или использовать режим транзакций по умолчанию.

Примеры с включенным и выключенным MVCC

Создайте файл init.lua со следующим содержимым:

fiber = require 'fiber'box.cfg{ listen = '127.0.0.1:3301', memtx_use_mvcc_engine = false }box.schema.user.grant('guest', 'super', nil, nil, {if_not_exists = true})tickets = box.schema.create_space('tickets', { if_not_exists = true })tickets:format({    { name = "id", type = "number" },    { name = "place", type = "number" },})tickets:create_index('primary', {    parts = { 'id' },    if_not_exists = true})

Подключитесь к экземпляру с помощью команды tt connect:

tt connect 127.0.0.1:3301

Затем попробуйте выполнить транзакцию с yield внутри:

box.atomic(function() tickets:replace{1, 429} fiber.yield() tickets:replace{2, 429} end)

Будет получено сообщение об ошибке:

---- error: Transaction has been aborted by a fiber yield...

Кроме того, если оставить транзакцию открытой при возврате из запроса, будет выдано сообщение об ошибке:

127.0.0.1:3301> box.begin()    ⨯ Failed to execute command: Transaction is active at return from function

Измените значение memtx_use_mvcc_engine на true, перезапустите Tarantool и попробуйте снова:

127.0.0.1:3301> box.atomic(function() tickets:replace{1, 429} fiber.yield() tickets:replace{2, 429} end)---...

Теперь проверим, успешна ли эта транзакция:

127.0.0.1:3301> box.space.tickets:select({}, {limit = 10})---- - [1, 429]  - [2, 429]...

Потоки и интерактивные транзакции

Начиная с 2.10.0, IPROTO реализует потоки (streams) и интерактивные транзакции, которые можно использовать при включенном на сервере параметре memtx_use_mvcc_engine.

Поток

Поток (stream) поддерживает мультиплексирование нескольких транзакций через одно соединение. Каждый поток имеет собственный идентификатор, уникальный в пределах соединения. Все запросы с одинаковым ненулевым идентификатором потока принадлежат одному потоку. Все запросы в потоке выполняются строго последовательно. Это позволяет реализовать интерактивные транзакции. Если идентификатор потока запроса равен 0, запрос не принадлежит ни одному потоку и обрабатывается обычным образом.

В net.box поток – это объект над соединением, который реализует те же методы, но обеспечивает последовательное выполнение запросов. Идентификатор генерируется автоматически на стороне клиента. Если пользователь пишет собственный коннектор и хочет использовать потоки, ему необходимо передавать stream_id через протокол IPROTO.

В отличие от потока выполнения (thread), который связан с многозадачностью и выполнением внутри программы, поток (stream) в контексте протокола передает данные между клиентом и сервером.

Интерактивная транзакция

Интерактивная транзакция – это транзакция, которую не нужно отправлять в одном запросе. Существует несколько способов начать, зафиксировать и откатить транзакцию, и их можно комбинировать. Можно использовать stream:begin(), stream:commit(), stream:rollback() или соответствующие методы потока – call, eval или execute – с использованием синтаксиса транзакций SQL.

Создадим Lua-клиент (client.lua) и запустим его с помощью Tarantool:

local net_box = require 'net.box'local conn = net_box.connect('127.0.0.1:3301')local conn_tickets = conn.space.ticketslocal yaml = require 'yaml'local stream = conn:new_stream()local stream_tickets = stream.space.tickets-- Begin transaction over an iproto stream:stream:begin()print("Replaced in a stream\n".. yaml.encode(  stream_tickets:replace({1, 768}) ))-- Empty select, the transaction was not committed.-- You can't see it from the requests that do not belong to the-- transaction.print("Selected from outside of transaction\n".. yaml.encode(conn_tickets:select({}, {limit = 10}) ))-- Select returns the previously inserted tuple-- because this select belongs to the transaction:print("Selected from within transaction\n".. yaml.encode(stream_tickets:select({}, {limit = 10}) ))-- Commit transaction:stream:commit()-- Now this select also returns the tuple because the transaction has been committed:print("Selected again from outside of transaction\n".. yaml.encode(conn_tickets:select({}, {limit = 10}) ))os.exit()

Затем вызовите его и увидите следующий вывод:

Replaced in a stream--- [1, 768]...Selected from outside of transaction---- [1, 429]- [2, 429]...Selected from within transaction---- [1, 768]- [2, 429]...Selected again from outside of transaction---- [1, 768]- [2, 429]...```