Что такое deadlock и как его избежать?
Deadlock (взаимная блокировка) — ситуация, когда две или более транзакции блокируют друг друга, каждая ожидая ресурс, удерживаемый другой. PostgreSQL автоматически обнаруживает deadlock и откатывает одну из транзакций.
Пример
Пример
Транзакция 1: Транзакция 2:
BEGIN; BEGIN;
UPDATE accounts SET balance = 900 UPDATE accounts SET balance = 1100
WHERE id = 1; -- блокирует id=1 WHERE id = 2; -- блокирует id=2
UPDATE accounts SET balance = 1100 UPDATE accounts SET balance = 900
WHERE id = 2; -- ЖДЁТ id=2 WHERE id = 1; -- ЖДЁТ id=1
-- DEADLOCK!
PostgreSQL обнаруживает deadlock через детектор с периодом deadlock_timeout (по умолчанию 1 секунда) и откатывает одну из транзакций с ошибкой ERROR: deadlock detected.
Способы избежать deadlock
1. Единый порядок обращения к ресурсам:
Пример
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = LEAST(1, 2);
UPDATE accounts SET balance = balance + 100 WHERE id = GREATEST(1, 2);
COMMIT;
2. SELECT FOR UPDATE с сортировкой:
Пример
BEGIN;
SELECT * FROM accounts WHERE id IN (1, 2) ORDER BY id FOR UPDATE;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
3. Минимизировать время транзакций — не выполнять долгие вычисления или вызовы внешних сервисов внутри транзакции.
4. NOWAIT или SKIP LOCKED:
Пример
SELECT * FROM accounts WHERE id = 1 FOR UPDATE NOWAIT;
SELECT * FROM task_queue WHERE status = 'pending'
ORDER BY id LIMIT 1 FOR UPDATE SKIP LOCKED;
5. Уменьшить уровень изоляции — READ COMMITTED менее склонен к deadlock, чем SERIALIZABLE.
На собеседовании: главное правило предотвращения deadlock — единый порядок обращения к ресурсам. Если все транзакции блокируют строки в порядке возрастания ID, deadlock невозможен. Упомяните, что PostgreSQL обнаруживает deadlock автоматически, поэтому приложение должно уметь повторять откаченную транзакцию.