Короткий ответ
Deadlock возникает, когда две (или более) транзакции удерживают блокировки и одновременно ждут блокировку, удерживаемую друг другом, образуя цикл ожидания; PostgreSQL автоматически обнаруживает цикл и прерывает одну из транзакций с ошибкой deadlock detected, которую нужно перехватить и повторить операцию.
Как это работает подробнее
PostgreSQL периодически проверяет граф ожидания блокировок; при обнаружении цикла выбирается «жертва» — её запрос завершается ошибкой, а остальные участники продолжают работу.
Пример кода
example.py
from django.db import transaction, OperationalError
import time
def transfer_with_retry(from_id, to_id, amount, attempts=3):
for attempt in range(attempts):
try:
with transaction.atomic():
first, second = sorted([from_id, to_id])
# блокируем строки в фиксированном порядке, чтобы избежать deadlock
Account.objects.select_for_update().filter(id__in=[first, second])
Account.objects.filter(id=from_id).update(balance=F("balance") - amount)
Account.objects.filter(id=to_id).update(balance=F("balance") + amount)
return
except OperationalError as e:
if "deadlock detected" in str(e) and attempt < attempts - 1:
time.sleep(0.1 * (attempt + 1))
continue
raiseЧто использовать на практике
- Практика. Диагностируют через лог PostgreSQL (сообщения «deadlock detected» содержат оба конфликтующих запроса) или pg_stat_activity/pg_locks; снижают риск, блокируя строки в одном и том же порядке во всех частях кода (например, по возрастанию id) и держа транзакции короткими.
Подводные камни
- Ошибка. Не оборачивают операцию в retry-логику после перехваченной ошибки deadlock, хотя это ожидаемая ситуация; обновляют несколько строк в разном порядке в разных частях кода, создавая классические условия для deadlock.