Deadlock в PostgreSQL: одинаковый порядок обновления строк

Перевод денег обновляет два счёта: сначала отправителя, затем получателя. Два встречных перевода запускаются одновременно. Первый держит строку счёта 10 и ждёт счёт 20, второй держит 20 и ждёт 10. Ни один не может продолжить без освобождения строки другим.

Это взаимная блокировка, а не просто медленный запрос. PostgreSQL обнаруживает цикл ожиданий и прерывает одну транзакцию. Увеличение timeout не устраняет цикл. Надо согласовать порядок захвата строк для всех путей, которые меняют эти объекты.

Как изменить решение

BEGIN;
SELECT id FROM accounts
WHERE id IN (10, 20)
ORDER BY id
FOR UPDATE;
UPDATE accounts SET balance = balance - 50 WHERE id = 10;
UPDATE accounts SET balance = balance + 50 WHERE id = 20;
COMMIT;

Оба встречных перевода сначала пытаются заблокировать меньший id. Один ожидает, пока другой завершит транзакцию, но встречного ожидания на второй строке уже не возникает. Порядок бизнес-действий и порядок блокировок здесь разделены.

Правило должно действовать во всём приложении: отдельный административный скрипт с обратным порядком способен вернуть проблему. Если транзакция затрагивает несколько типов сущностей, полезно определить порядок и между таблицами. Блокировать следует только действительно необходимые строки, чтобы обычное ожидание не стало слишком широким.

Что проверить на своих данных

Для воспроизведения откройте два соединения и обновите первые счета в противоположном порядке, не фиксируя изменения. Затем отправьте вторые UPDATE. После проверки откатите тестовые изменения. В исправленном варианте повторите встречные переводы и убедитесь, что общая сумма балансов сохранилась.

Обработчик всё равно должен уметь повторить транзакцию после deadlock: сторонние ограничения и новые пути доступа могут создать другой цикл. Повторяют всю бизнес-операцию с заново прочитанными данными, а не только последний упавший UPDATE. Внешние действия вроде отправки платежа нельзя бесконтрольно дублировать вместе с таким повтором.

Практика дальше

Границы транзакции проще держать под контролем в законченной структуре приложения. Курс FastAPI ведёт через backend с SQLAlchemy к тестам через pytest, fixtures и dependency_overrides. Практика на SQLite позволяет закрепить жизненный цикл сессии и ответы обработчиков.

Посмотреть программу и практику курса

Источник: Deadlock в PostgreSQL — официальная документация.

Другие задачи по теме: PostgreSQL: запись, блокировки и планы запросов.

Продолжить на практике