Ответ
Deadlock в базах данных — это ситуация, когда две или более транзакции взаимно блокируют друг друга, каждая ожидая ресурс (например, строку или таблицу), который уже заблокирован другой транзакцией. В результате ни одна из них не может продолжиться.
Пример сценария:
-- Транзакция 1
BEGIN TRANSACTION;
-- Блокирует строку с id = 1 для обновления
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- Пытается обновить строку с id = 2, но она заблокирована Транзакцией 2
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- Транзакция 2 (выполняется одновременно)
BEGIN TRANSACTION;
-- Блокирует строку с id = 2 для обновления
UPDATE accounts SET balance = balance - 50 WHERE id = 2;
-- Пытается обновить строку с id = 1, но она заблокирована Транзакцией 1
UPDATE accounts SET balance = balance + 50 WHERE id = 1;
COMMIT;
Как СУБД решает проблему:
Современные СУБД (например, PostgreSQL, MySQL) имеют встроенный механизм обнаружения deadlock. Они строят граф зависимостей транзакций и, обнаружив цикл, принудительно завершают одну из транзакций (выбирая «жертву», как правило, по наименьшему объему выполненной работы), откатывая её (ROLLBACK
) и возвращая ошибку приложению. Приложение должно быть готово обработать эту ошибку и, возможно, повторить транзакцию.
Способы предотвращения на уровне приложения:
- Соблюдение последовательности: Всегда обращайтесь к ресурсам (таблицам, строкам) в одном и том же порядке во всех транзакциях.
- Короткие транзакции: Держите транзакции как можно более короткими, чтобы минимизировать время удержания блокировок.
- Использование адекватного уровня изоляции: Более низкие уровни изоляции (например,
READ COMMITTED
) создают меньше блокировок, чемSERIALIZABLE
. - Явные блокировки с таймаутами: Использовать
SELECT ... FOR UPDATE NOWAIT
илиSKIP LOCKED
, чтобы не ждать заблокированную строку, а сразу получить ошибку или пропустить её.