Во время формирования отчёта чтение нескольких связанных таблиц должно отражать единый момент времени, хотя записи продолжаются. Какой механизм транзакционной СУБД это обеспечивает?
Единый согласованный срез данных обеспечивает транзакция с изоляцией на основе снимка, обычно реализованная через MVCC. СУБД фиксирует видимые транзакции на момент начала чтения, поэтому все запросы внутри такой транзакции видят согласованное состояние, даже если параллельно выполняются записи.
При блокировочной модели чтение больших объёмов могло надолго блокировать изменения, а изменения — задерживать чтение. Это особенно плохо для отчётов, которые выполняются дольше обычных транзакций.
MVCC решает проблему иначе: вместо ожидания блокировок СУБД хранит версии строк и позволяет читателю работать с подходящей версией данных. Конкретные реализации различаются, но общий принцип состоит в разделении версий данных и фиксации правил их видимости.
Если отчёт читает таблицы по отдельности при обычной изоляции Read Committed, каждый запрос может получить состояние базы на немного различающийся момент времени. Например, данные о заказах будут прочитаны до изменения клиента, а данные о клиентах — уже после него.
Так возникают несогласованные итоги: сумма заказов может не соответствовать набору клиентов, агрегаты по связанным сущностям могут расходиться, а повторное чтение той же строки может вернуть другое значение. Простое выполнение нескольких запросов подряд не гарантирует общего момента времени.
Отчёт выполняется внутри одной read-only транзакции с уровнем изоляции, который предоставляет стабильный снимок. При создании снимка СУБД определяет, какие версии строк доступны этой транзакции; последующие изменения получают новые версии и не меняют уже выбранный срез.
При MVCC читатель обычно не блокирует записывающие транзакции. Для каждой строки СУБД может хранить служебные сведения о транзакции-источнике версии и выбирать последнюю версию, допустимую правилами снимка. Незакоммиченные изменения и версии, появившиеся после снимка, для такого чтения невидимы.
Важно отличать уровни изоляции. Read Committed часто создаёт новый снимок для каждого оператора, поэтому несколько операторов одной транзакции не обязательно видят одну картину. Для единого среза нужен режим, гарантирующий повторяемое или снимочное чтение; название и точная семантика зависят от СУБД.
Снимок не означает полной сериализуемости. Он защищает от изменения уже видимых строк в рамках чтения, но при некоторых сценариях допускает аномалию перекрёстной записи, известную как write skew. Если требуется поведение, эквивалентное последовательному выполнению транзакций, нужен сериализуемый уровень изоляции или дополнительные ограничения.
Длинное чтение имеет цену: старые версии строк приходится сохранять дольше, что увеличивает нагрузку на хранение, очистку версий и иногда на журнал транзакций. Поэтому большие отчёты обычно выносят в аналитическую систему или запускают на специально предназначенной реплике, но сама реплика должна предоставлять согласованный снимок.
Для нескольких независимых хранилищ одной локальной транзакции недостаточно. Там требуется отдельный механизм согласования среза: общий идентификатор версии, временная отметка согласованности или публикация данных через поток с определённой семантикой. Простое совпадение времени чтения не гарантирует атомарность между системами.
Финансовый отчёт читает заказы, платежи и сведения о клиентах из транзакционной базы. Во время его выполнения платежи продолжают подтверждаться, а обычное чтение каждого раздела отчёта может зафиксировать разные состояния.
Рассматривались три варианта. Блокировка таблиц дала бы согласованность, но задерживала бы рабочие записи. Чтение из отдельной реплики снизило бы нагрузку на основной узел, однако добавило бы задержку репликации и не решило бы проблему без гарантии согласованного снимка. Предварительно рассчитанная витрина уменьшила бы нагрузку, но отчёт не всегда получил бы самые свежие данные.
Выбран read-only запуск отчёта в транзакции со стабильным снимком на узле, предназначенном для аналитического чтения. Это устранило расхождения между связанными таблицами без блокировки обычных записей; взамен команда ограничила длительность отчётов и контролировала накопление старых версий.
Нет. Результат зависит от уровня изоляции: при Read Committed отдельные операторы могут получать разные снимки. Нужна явно определённая семантика стабильного чтения, а не только факт наличия транзакционной границы.
Снимок делает чтение согласованным относительно выбранного момента, но не всегда запрещает конфликтующие записи. Две транзакции могут независимо увидеть старое состояние и выполнить изменения, которые вместе нарушат бизнес-инвариант. Для таких правил применяют сериализуемую изоляцию, блокировки или явные ограничения на уровне данных.
Он удерживает границу видимости старых версий. Пока снимок активен, очистка не может удалить версии, которые ещё могут понадобиться читателю; растут объём хранения, работа фоновой очистки и нагрузка на журнал. Поэтому длительные аналитические чтения ограничивают по времени или направляют в систему, оптимизированную для таких нагрузок.