Очередь блокировок честная, поэтому обычное ожидание рано или поздно разрешается. Исключение - когда дождаться нельзя в принципе: A держит строку 1 и хочет строку 2, B держит строку 2 и хочет строку 1. Это взаимоблокировка, и сама она не рассосётся.
Граф ожидания
Вершины - транзакции, ребро A → B значит «A ждёт то, что держит B».
Пока это цепочки и деревья, всё разрешается. Цикл (T1 → T2 → T1)
означает вечное ожидание. Обнаружение deadlock - это поиск цикла.
Как находится
Строить граф на каждую блокировку дорого, а почти все ожидания
разрешаются сами. Поэтому при постановке в очередь взводится таймер
deadlock_timeout (по умолчанию 1 с). Если за это время блокировку не
выдали - запускается детектор и ищет цикл. Нашёл - откатывает жертву;
нет - ждёт дальше. Отсюда задержка между тупиком и его разрывом ≈ 1 с.
Сообщение
ERROR: deadlock detected
DETAIL: Process 412 waits for ShareLock on transaction 1033; blocked by process 410.
Process 410 waits for ShareLock on transaction 1034; blocked by process 412.
CONTEXT: while updating tuple (0,5) in relation "bookings"
SQLSTATE - 40P01. Жертва получает полный ROLLBACK (вся транзакция,
не последний запрос), вторая продолжает. Счётчик
pg_stat_database.deadlocks растёт на единицу.
Неочевидные источники
Двух явных UPDATE не нужно. Один UPDATE на несколько строк в разном
порядке обхода даёт тот же цикл (порядок задаёт план, не порядок в
IN). Перекрёстные вставки по внешнему ключу - тоже.
Как избежать
- единый порядок блокировок (
ORDER BY idвFOR UPDATE) делает граф ациклическим - цикл становится невозможен; - короткие транзакции сужают окно пересечения;
- ретрай на
40P01: deadlock штатен, приложение обязано повторять.
В отличие от idle-in-transaction-cascade, deadlock разрывает сам сервер. За очередью блокировок стоит механизм из relation-locks и row-locks.