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: запись, блокировки и планы запросов.
Продолжить на практике
- FastAPI для начинающих: API с базой данных и тестами ↗
Соберёте свой первый полноценный backend на FastAPI: с базой данных, регистрацией, JWT-авторизацией и автотестами. 34 коротких урока и 140 заданий с проверкой — бесплатно.
