Какие последствия может вызвать длительная транзакция, которая только читает данные, в СУБД с MVCC?
В СУБД с MVCC длительная читающая транзакция может удерживать старую версию снимка данных. Поэтому фоновая очистка не сможет удалить версии строк, которые потенциально ещё нужны этому снимку, что приводит к росту занимаемого места, дополнительным затратам на чтение и снижению производительности.
MVCC появился как способ уменьшить конкуренцию между чтением и изменением данных. Читающая транзакция получает согласованный снимок и обычно не блокирует запись, а записывающая транзакция создаёт новую версию строки.
Такой подход переносит часть сложности с блокировок на управление версиями: старые версии нужно сохранять, пока они могут быть видны активным снимкам, а затем безопасно удалять.
Транзакция может начать чтение, получить старый снимок и затем долго оставаться открытой — например, из-за зависшего отчёта или соединения, оставленного в состоянии idle in transaction. Даже если она больше не выполняет запросы, СУБД может считать, что ей всё ещё потенциально нужны версии, существовавшие на момент создания снимка.
Если очистка удалит такие версии слишком рано, транзакция сможет потерять согласованность чтения. Поэтому СУБД сохраняет их, а накопление версий увеличивает объём хранения и стоимость обработки запросов.
При изменении строки MVCC-система обычно не уничтожает старое состояние немедленно, а создаёт новую версию. Читающая транзакция определяет, какие версии видимы ей по правилам своего снимка и уровня изоляции.
Очистка старых версий возможна только после того, как ни один активный снимок уже не сможет их увидеть. Длительная транзакция с ранним снимком может удерживать этот нижний предел видимости, поэтому версии, созданные другими транзакциями позже, продолжают накапливаться.
Практические последствия:
Точный механизм зависит от СУБД. В PostgreSQL длительный снимок может препятствовать удалению старых версий строк процессом VACUUM. В других системах используются собственные журналы, undo-сегменты или version store, поэтому детали и симптомы отличаются.
Обычно проблему решают ограничением времени транзакций, закрытием транзакций сразу после завершения чтения, контролем таймаутов и поиском соединений, долго находящихся в открытой транзакции. Увеличение дискового пространства или более частый запуск очистки устраняет симптомы лишь частично, если причина — постоянно удерживаемый снимок.
Ночной отчёт открывает транзакцию, читает несколько таблиц и затем формирует результат в течение часа. В это время приложение активно обновляет те же таблицы, а свободное место быстро уменьшается: старые версии не могут быть полностью очищены.
Вариант с переходом на блокирующее чтение уменьшит количество сохраняемых версий не гарантированно, зато создаст ожидания для записывающих транзакций и ухудшит доступность. Увеличение ресурсов позволит дольше работать до проявления проблемы, но не устранит удержание старых версий.
Выбранное решение — сократить транзакцию до непосредственного чтения согласованного набора данных, выполнять длительное формирование отчёта уже после завершения транзакции и установить контроль длительности открытых транзакций. Это сохраняет согласованность полученного набора и не удерживает снимок во время тяжёлой обработки. В результате очистка снова удаляет устаревшие версии, а рост хранилища стабилизируется.
Нет. В системе с MVCC читающая транзакция обычно не удерживает обычную блокировку, несовместимую с обновлением строки. Её вред может проявляться не в ожиданиях блокировок, а в невозможности удалить старые версии, поэтому диагностика только по блокировкам проблему не обнаружит.
После первого запроса у неё может сохраниться снимок или минимальная граница видимости. Пока транзакция не завершена, СУБД обязана исходить из того, что следующий запрос может обратиться к данным через этот снимок. Поэтому отсутствие текущей активности не означает, что ресурсы, связанные со снимком, уже можно освободить.
Это зависит от длительности отдельных операторов и реализации СУБД. При READ COMMITTED новый снимок часто создаётся для каждого оператора, поэтому короткие операторы могут быстрее освобождать старые версии. Однако открытая транзакция, уже выполнившая запрос, всё равно может удерживать связанные с ней ресурсы в конкретной СУБД; кроме того, переход меняет семантику чтения и может допустить неповторяемые результаты. Уровень изоляции нельзя менять без проверки требований к согласованности отчёта.