# idle-in-transaction и каскад блокировок _Блокировки · PostgreSQL Knowledge Base_ **TL;DR:** Забытый открытый BEGIN держит блокировку до конца транзакции. Если за ним в честной FIFO-очереди встанет ALTER TABLE (ACCESS EXCLUSIVE), то даже совместимые SELECT за ним тоже повиснут - встаёт вся таблица. Это самый частый способ положить базу одной строкой. Разберём по шагам, как безобидное сочетание превращается в каскад. ## Сцена из трёх сессий ```sql -- A: открыл транзакцию, прочитал, забыл закрыть BEGIN; SELECT count(*) FROM flights; -- ACCESS SHARE; state = idle in transaction -- B: выкатывается миграция ALTER TABLE flights ADD COLUMN gate text; -- хочет ACCESS EXCLUSIVE → ждёт A -- C: обычный пользовательский запрос SELECT * FROM flights WHERE flight_no = 'PG1'; -- ACCESS SHARE → ждёт B ``` `A` и `C` совместимы - два читателя. Но между ними стоит `B` с ACCESS EXCLUSIVE, а очередь блокировок честная: новые запросы не перепрыгивают ждущего. Поэтому `C` виснет за `B`, а `B` - за `A`. Один забытый `BEGIN` плюс одна миграция останавливают всю таблицу. ## В чём именно ловушка Опасен не `ALTER TABLE` сам по себе, а его сочетание с долгой открытой транзакцией впереди. Тяжёлая блокировка держится до конца транзакции (см. [relation-locks](/courses/postgres/kb/relation-locks.md)), а не до конца запроса. Сессия в состоянии `idle in transaction` ничего не делает, но всё ещё держит ACCESS SHARE. ## Диагностика ```sql SELECT w.pid AS waiting, b.pid AS blocking, b.state, b.query FROM pg_stat_activity w JOIN LATERAL unnest(pg_blocking_pids(w.pid)) AS bp(pid) ON true JOIN pg_stat_activity b ON b.pid = bp.pid WHERE cardinality(pg_blocking_pids(w.pid)) > 0; ``` `pg_blocking_pids(pid)` сразу называет корень. Если виновник в `idle in transaction`, снять его `pg_cancel_backend` чаще всего не выйдет - там нет активного запроса. Нужен `pg_terminate_backend`: он закроет сессию, транзакция откатится, очередь рассосётся. ## Профилактика - `idle_in_transaction_session_timeout` прибивает забытые транзакции; - `lock_timeout` не даёт DDL стоять в очереди бесконечно; - короткие транзакции и `COMMIT` сразу после работы. В отличие от [deadlocks](/courses/postgres/kb/deadlocks.md), каскад - это не цикл: его не разрывает детектор, он будет стоять, пока не вмешаешься. ## Команды ```sql SELECT pid, state, query FROM pg_stat_activity WHERE state = 'idle in transaction'; ``` Найти забытые открытые транзакции ```sql SELECT pg_terminate_backend(); ``` Закрыть сессию-блокировщик: транзакция откатится, блокировки спадут ```sql ALTER SYSTEM SET idle_in_transaction_session_timeout = '30s'; ``` Автоматически закрывать транзакции, висящие без активности ## См. также - [Тяжёлые блокировки отношений](/courses/postgres/kb/relation-locks.md) - [Блокировки строк](/courses/postgres/kb/row-locks.md) - [Взаимоблокировки (deadlock)](/courses/postgres/kb/deadlocks.md)