При многоверсионном чтении снимок транзакции создан до её UPDATE: как она видит изменённую ею строку при по...

При многоверсионном чтении снимок транзакции создан до её UPDATE: как она видит изменённую ею строку при повторном чтении?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Транзакция должна видеть собственное изменение при повторном чтении, даже если исходный снимок был создан раньше. При построении результата СУБД учитывает не только версии, видимые из снимка, но и изменения, выполненные самой текущей транзакцией.

Это правило называют read-your-writes — чтением собственных записей. Иначе транзакция могла бы после успешного изменения продолжать видеть старое состояние, что нарушило бы ожидаемую последовательность её операций.

Исторический контекст

MVCC появился как способ уменьшить конфликт между читателями и писателями: читатели получают подходящие версии строк, а писатели создают новые версии вместо немедленного блокирования всех чтений. Это повышает конкурентность по сравнению с моделью, где чтение каждой изменяемой строки обязательно ждёт завершения записи.

Однако одного снимка недостаточно. Если читать только версии, существовавшие на момент его создания, транзакция не увидела бы собственные последующие изменения. Поэтому реализации MVCC дополняют правила видимости специальной обработкой записей текущей транзакции.

Постановка проблемы

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

Если при повторном чтении СУБД безусловно применит только старый снимок, транзакция увидит прежнее значение собственной строки. Это приводит к логическим ошибкам: последующие вычисления могут использовать данные, которые сама транзакция уже изменила.

Подробное решение

При чтении СУБД обычно рассматривает несколько условий видимости. Версия должна быть видима согласно снимку, либо она должна быть создана текущей транзакцией и ещё не отменена. Вторая часть специально позволяет транзакции видеть собственные вставки и обновления.

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

Это не означает, что собственные изменения становятся видимыми всем немедленно. Изоляция сохраняет границу между незавершёнными изменениями разных транзакций, тогда как текущая транзакция получает право видеть свои результаты.

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

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

Ситуация из практики

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

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

Правильное решение — читать данные в той же транзакции и полагаться на правило видимости собственных изменений. Если аудит должен переживать откат основной операции, его следует организовывать отдельно, например через надёжную очередь после фиксации, поскольку read-your-writes не делает внешние действия атомарными с транзакцией.

Что кандидаты часто упускают

  1. Вопрос: Видит ли другая транзакция изменение, которое текущая транзакция видит благодаря read-your-writes?

Ответ: Нет, не обязательно. Правило относится только к текущей транзакции. Другая транзакция увидит изменение после его фиксации и согласно своим правилам изоляции; до этого она может видеть старую версию, ждать блокировку или получить конфликт.

  1. Вопрос: Исчезает ли собственное изменение из результата, если транзакция позже откатывает его до сохранённой точки?

Ответ: Да. После отката к точке сохранения изменения, выполненные после неё, больше не считаются изменениями текущего логического состояния транзакции. Повторное чтение должно учитывать состояние до отменённых операций, а изменения, оставшиеся до точки сохранения, продолжают быть видимыми самой транзакции.

  1. Вопрос: Почему read-your-writes не гарантирует, что повторное чтение покажет актуальное состояние всей базы?

Ответ: Это правило гарантирует видимость собственных изменений, но не обновляет снимок для чужих строк. При изоляции, где снимок закреплён за всей транзакцией, повторное чтение может продолжать показывать старые версии данных, изменённых и зафиксированных другими транзакциями после создания снимка. Поэтому видимость собственных записей и актуальность всех данных — разные свойства.