Почему длительный запрос чтения может блокировать изменение структуры таблицы, даже если он не блокирует читаемые строки?
Потому что блокировки строк и блокировки структуры объекта — разные механизмы. Читающая транзакция обычно удерживает метаданные о таблице до завершения операции или транзакции, а изменение структуры требует несовместимой блокировки на тот же объект. Поэтому чтение строк может не мешать UPDATE, но задерживать ALTER TABLE, удаление или изменение индекса.
MVCC и блокировки строк появились, чтобы чтение и изменение данных могли выполняться параллельно без постоянной блокировки всей таблицы. Однако СУБД должна одновременно гарантировать целостность системного каталога: запрос не должен продолжать работать с таблицей, пока её структура меняется в несовместимом виде.
Для этого отдельно применяются блокировки схемы или метаданных. Их точные названия, совместимость и длительность зависят от СУБД, но исходная задача одинакова: согласовать выполнение запросов с операциями изменения структуры.
Отчёт может читать миллионы строк внутри одной долгой транзакции. Параллельно администратор запускает изменение столбца, удаление индекса или перестроение таблицы, рассчитывая, что MVCC позволит операции пройти независимо.
Если DDL требует эксклюзивного доступа к метаданным, он будет ждать завершения отчёта. Последствия — задержка миграции, рост очереди ожидающих операций, блокировка последующих запросов и риск исчерпания времени ожидания.
При начале чтения СУБД получает совместимую блокировку на объект схемы либо регистрирует зависимость от его текущего определения. Эта блокировка не обязательно запрещает изменение строк: другая транзакция может выполнять INSERT, UPDATE или DELETE, если их блокировки совместимы.
Операция изменения структуры обычно требует более сильной, часто эксклюзивной, блокировки. Она должна исключить ситуацию, когда один запрос использует старое описание таблицы, а другой уже изменяет это описание или физическую организацию объекта.
MVCC не отменяет блокировки метаданных. Снимки версий строк отвечают за видимость данных, но не решают задачу согласованного доступа к схеме. Поэтому фраза «читатели не блокируют писателей» применима не ко всем видам операций.
Точная модель зависит от СУБД. В одних системах блокировка удерживается до конца оператора, в других — до конца транзакции; некоторые варианты изменения структуры поддерживают ограниченный режим параллельной работы. Нельзя переносить поведение конкретной СУБД на весь SQL без проверки документации.
Практические меры:
idle in transaction;Компромисс очевиден: короткие транзакции уменьшают блокировки, но могут дать отчёту менее цельный снимок данных; онлайн-операции снижают простой, но обычно сложнее и могут потреблять больше ресурсов.
Отчётная транзакция читает большую таблицу и удерживает соединение открытым. Команда запускает миграцию, которая должна изменить структуру этой таблицы. Миграция не меняет строки, но ждёт блокировку метаданных, а новые запросы к таблице начинают накапливаться за ожидающей операцией.
Вариант «принудительно убить отчёт» быстро освобождает блокировку, но приводит к потере результата и может нарушить работу пользователя. Вариант «ждать бесконечно» сохраняет отчёт, но создаёт неуправляемую очередь и усложняет диагностику.
Рациональное решение — ограничить время жизни отчётных транзакций, запретить длительное бездействие внутри транзакции и запускать миграцию с контролируемым тайм-аутом. Для большой таблицы выбирают совместимую или поэтапную миграцию, заранее проверяют активные транзакции и при необходимости переносят отчётную нагрузку на реплику. Так уменьшается вероятность простоя без ложного предположения, что MVCC защищает от всех блокировок.
1. Может ли обычный SELECT блокировать UPDATE строк?
Обычно при MVCC обычное чтение не удерживает блокировку, несовместимую с изменением конкретной строки, поэтому UPDATE может продолжаться. Но это не универсальное правило: режим блокирующего чтения, выбранный уровень изоляции, отсутствие MVCC или особенности конкретного движка могут изменить поведение.
Главное — различать блокировку данных, блокировку диапазона и блокировку метаданных. Нельзя делать вывод о всех конфликтах только по тому, что запрос является SELECT.
2. Почему завершение самого оператора чтения иногда не освобождает блокировку?
Потому что срок жизни блокировки определяется не только оператором, но и границей транзакции. Если чтение выполняется внутри явной транзакции, соединение может удерживать связанные с объектом блокировки до COMMIT или ROLLBACK, даже когда оператор уже вернул строки.
Именно поэтому состояние idle in transaction опасно: приложение ничего не выполняет, но транзакция ещё не завершена. Проверять нужно не только активные запросы, но и длительность открытых транзакций.
3. Всегда ли онлайн-изменение схемы полностью устраняет блокировки?
Нет. Онлайн-режим обычно уменьшает время эксклюзивной блокировки, но не гарантирует её полного отсутствия. Операции всё равно могут кратко блокировать метаданные в начале или конце, конкурировать за ресурсы, ждать старые транзакции или создавать нагрузку на журнал и ввод-вывод.
Поэтому онлайн-миграцию также запускают с тайм-аутами, наблюдением за блокировками и планом отката. Её преимущество — снижение окна конфликта, а не превращение DDL в полностью независимую от транзакций операцию.