lesson ── postgres-labs ── ~22 мин ── 5 шагов
Сначала ты заставишь бэкенд ждать тяжёлую блокировку и опознаешь это
по wait_event. Потом откроешь транзакцию на уровне Serializable и
найдёшь её предикатные блокировки SIReadLock - те, что ничего не
блокируют, а только метят прочитанное.
Вкладки: psql-a, psql-b - две сессии, psql-ops - наблюдатель.
В каждой сначала запусти psql.
интерактивный sandbox
Поднимется контейнер postgreslab/postgres-base с PostgreSQL 17 и psql. В браузере откроется терминал, база lab уже настроена. Каждый шаг проверяется автоматически. Сеть air-gapped, наружу контейнер не ходит.
stack ── PostgreSQL 17 · psql · 1 GB RAM · air-gapped · самоуничтожается через 45 мин простоя
Вкладка psql-a, запусти psql:
BEGIN;
SELECT * FROM bookings WHERE book_ref = '000001' FOR UPDATE;
Строка под замком FOR UPDATE. Транзакцию держим открытой.
FOR UPDATE - сильнейший режим блокировки строки.
✓ A держит строку 000001.
Вкладка psql-b, запусти psql:
BEGIN;
SELECT * FROM bookings WHERE book_ref = '000001' FOR UPDATE;
Команда повисла. Предскажи wait_event_type для B, прежде чем
проверять.
Ожидание тяжёлой блокировки относится к типу Lock.
✓ B стоит с wait_event_type = Lock - ждёт строку.
Вкладка psql-ops, запусти psql и посмотри, на чём стоят бэкенды:
SELECT pid, state, wait_event_type, wait_event
FROM pg_stat_activity WHERE state != 'idle';
B показывает Lock / transactionid - ждёт завершения A. Теперь
освободи очередь: в psql-a сделай COMMIT, затем в psql-b,
когда строка достанется ему, сделай ROLLBACK. Проверим, что
никто не ждёт.
После COMMIT в A блокировка строки спадает, и B получает её.
✓ Очередь пуста. Переходим к предикатным блокировкам.
Вкладка psql-a (psql уже запущен). Открой транзакцию на
сильнейшем уровне изоляции и прочитай данные:
BEGIN ISOLATION LEVEL SERIALIZABLE;
SELECT count(*) FROM flights WHERE departure = 'SVO';
Чтение на Serializable оставляет метки SIReadLock - «я это
читала». Они никого не блокируют. Оставь транзакцию открытой и
предскажи: появятся ли строки SIReadLock в pg_locks?
SIReadLock возникает именно при чтении на уровне Serializable.
✓ Предикатные блокировки появились - чтение оставило следы.
Вкладка psql-ops:
SELECT locktype, relation::regclass, mode
FROM pg_locks WHERE mode = 'SIReadLock';
Метки стоят на flights - на кортежах, странице или всей таблице
(PostgreSQL огрубляет гранулярность, когда строк много). Это
механизм обнаружения, а не ожидания: если другая транзакция
запишет туда, где стоит метка, и шаблон окажется опасным, на
коммите будет ошибка 40001. Закоммить транзакцию A (COMMIT).
relation в pg_locks для SIReadLock указывает на flights.
✓ Предикатные блокировки стоят на flights. SSI следит за чтениями.
Под тяжёлыми блокировками лежат лёгкие LWLock и spinlock - их видно только через wait_event, не в pg_locks. Ожидание тяжёлой блокировки - это wait_event_type = Lock. Предикатные блокировки SIReadLock нужны Serializable и не блокируют: это метки прочитанного для поиска опасных зависимостей, конфликт по ним приходит ошибкой 40001 на коммите.
команды
SELECT wait_event_type, wait_event FROM pg_stat_activity WHERE state='active';на чём ждут бэкендыSELECT mode FROM pg_locks WHERE mode='SIReadLock';предикатные блокировки SSIBEGIN ISOLATION LEVEL SERIALIZABLE;включить SSI, чтения метят SIReadLockконцепции