Дедлок (deadlock, взаимная блокировка) — ситуация, при которой два или более процесса или транзакции находятся в состоянии циклического ожидания: каждый удерживает ресурс, необходимый следующему, и ждёт освобождения ресурса, удерживаемого предыдущим. Ни один не может продолжить работу без внешнего вмешательства.
Четыре условия Коффмана
Дедлок возможен только при одновременном выполнении четырёх условий (Coffman conditions, 1971):
- Mutual Exclusion (взаимное исключение) — ресурс используется только одним процессом одновременно.
- Hold and Wait (удержание и ожидание) — процесс удерживает один ресурс и ожидает освобождения другого.
- No Preemption (нет вытеснения) — ресурс нельзя принудительно забрать у процесса.
- Circular Wait (кольцевое ожидание) — цепочка ожидания замкнута: A ждёт B, B ждёт C, C ждёт A.
Нарушение любого одного условия предотвращает дедлок.
Дедлоки в PostgreSQL
PostgreSQL автоматически обнаруживает дедлоки и прерывает одну из транзакций с ошибкой ERROR: deadlock detected. Параметр deadlock_timeout (по умолчанию 1 секунда) — время ожидания блокировки до проверки на дедлок. Пример ошибки в логах:
ERROR: deadlock detected
DETAIL: Process 12345 waits for ShareLock on transaction 678;
blocked by process 23456.
Process 23456 waits for ShareLock on transaction 789;
blocked by process 12345.
HINT: See server log for query details.
CONTEXT: while updating tuple (0,42) in relation "orders"
Типичная причина дедлоков в PHP/Python приложениях: параллельные транзакции обновляют строки в разном порядке. Транзакция 1: UPDATE orders SET status='paid' WHERE id=5; затем UPDATE orders SET status='paid' WHERE id=10. Транзакция 2 одновременно: UPDATE orders SET status='paid' WHERE id=10; затем UPDATE orders SET status='paid' WHERE id=5. Оба заблокируют друг друга. Решение: всегда обрабатывать записи в одном и том же порядке (по первичному ключу по возрастанию).
Дедлоки в MySQL/InnoDB
MySQL InnoDB также обнаруживает дедлоки автоматически. Последний зафиксированный дедлок: SHOW ENGINE INNODB STATUS → секция «LATEST DETECTED DEADLOCK». InnoDB выбирает жертву — транзакцию с наименьшей стоимостью отката. Параметр innodb_deadlock_detect (MySQL 5.7.15+): можно отключить при высокой нагрузке, если использовать innodb_lock_wait_timeout как альтернативу.
История
Формальное описание дедлоков и четыре необходимых условия сформулировал Эдвард Коффман (Edward Coffman) в 1971 году. Проблема дедлоков исследовалась с первых многозадачных ОС 1960-х. «Задача обедающих философов» Дейкстры (1965) — классический пример потенциального дедлока: 5 философов, 5 вилок, каждый берёт сначала левую, потом правую — возможно кольцевое ожидание.
Профилактика дедлоков в хостинге
На серверах с PostgreSQL или MySQL дедлоки проявляются при пиковой нагрузке на нагруженных интернет-магазинах. Диагностика в PostgreSQL: SELECT * FROM pg_locks l JOIN pg_stat_activity a ON l.pid = a.pid WHERE NOT granted; — незавершённые блокировки в реальном времени. Профилактика:
- Использовать короткие транзакции — удерживать блокировки минимальное время.
- Обновлять записи в детерминированном порядке — всегда по возрастанию id.
- Минимизировать SELECT FOR UPDATE — брать только те строки, которые реально нужно изменить.
- Оптимистичные блокировки (versioning через поле updated_at) вместо пессимистичных — конфликт обнаруживается при коммите, не при старте транзакции.
- Мониторинг через Prometheus: метрика
pg_stat_activity_count{state="waiting"}.