deшка
Урок 2.12

Транзакции и изоляция в объёме загрузок

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

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

Атомарность: всё или ничего

BEGIN;
DELETE FROM fact_payment WHERE paid_at >= '2026-06-30' AND paid_at < '2026-07-01';
INSERT INTO fact_payment SELECT ... WHERE paid_at >= '2026-06-30' AND paid_at < '2026-07-01';
COMMIT;

Если INSERT упадёт, DELETE откатится вместе с ним, и витрина останется в состоянии до запуска. Без транзакции сбой между двумя командами оставил бы день пустым. Любой шаг загрузки, который состоит из нескольких команд, оборачивается в транзакцию. В Airflow это означает одно соединение и явный COMMIT в конце, а не отдельная задача на каждую команду.

Ошибка внутри транзакции в PostgreSQL переводит её в состояние «только откат»: последующие команды отвергаются до ROLLBACK. Частичный откат делается через точки сохранения: SAVEPOINT s1, затем ROLLBACK TO s1 возвращает к отметке, не отменяя всё.

Что видит читатель: MVCC

PostgreSQL не блокирует чтение записью. Каждая транзакция видит снимок данных на момент своего начала, а изменения других транзакций становятся видны только после их COMMIT. Это называется многоверсионностью, MVCC.

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

Обратная сторона MVCC: удалённые и обновлённые строки не исчезают физически, а помечаются мёртвыми. Их убирает VACUUM. Таблица, которую каждый день перезаписывают целиком, без вакуума растёт в размере в разы. В модуле Greenplum это отдельный урок про bloat.

Уровни изоляции: что реально нужно знать

По умолчанию PostgreSQL работает в режиме READ COMMITTED: каждая команда внутри транзакции видит всё, что было зафиксировано к моменту её начала. Два SELECT подряд в одной транзакции могут увидеть разные данные, если между ними кто-то сделал COMMIT.

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

SERIALIZABLE гарантирует, что результат параллельных транзакций эквивалентен какому-то их последовательному выполнению, ценой откатов при конфликте. В аналитических загрузках почти не используется. Грязное чтение, READ UNCOMMITTED, в PostgreSQL не существует вовсе.

Что при этом происходит в других системах: Greenplum наследует модель PostgreSQL. ClickHouse транзакций в привычном смысле не имеет, атомарна вставка одного блока, и идемпотентность там строится на движках таблиц и дедупликации. Spark с Delta или Iceberg даёт атомарную замену партиции через метаданные таблицы.

Блокировки, которые ловит пайплайн

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

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

Диагностика: представление pg_stat_activity показывает, какие сессии чего ждут, pg_locks показывает сами блокировки. В Greenplum у долгих транзакций есть ещё одна цена: пока они открыты, VACUUM не может убрать мёртвые строки.

Практика

Песочница этого урока свободная: в ней одно соединение, поэтому параллельные транзакции показать нельзя, но атомарность и откат DDL видны. Выполните стартовый сценарий по шагам, попробуйте убрать ROLLBACK или заменить его на COMMIT и посмотрите на результат последнего запроса. База откатится к исходному состоянию кнопкой «сбросить базу».

транзакции · свободная песочница
Ctrl+Enter
Войти, чтобы отмечатьдальше →
2.13 →Качество данных и сверки