Что такое deadlock (взаимная блокировка) в SQL?

Ответ

Deadlock (взаимная блокировка) — это ситуация в многопользовательской СУБД, когда две или более транзакции бесконечно ожидают друг друга, каждая удерживая блокировку на ресурсе, который требуется другой для продолжения работы. Это создаёт циклическую зависимость.

Типичный сценарий на примере PostgreSQL:

-- ТРАНЗАКЦИЯ A
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1; -- Блокирует строку user_id=1
-- Транзакция A теперь ждет блокировку на user_id=2...

-- ТРАНЗАКЦИЯ B (выполняется параллельно)
BEGIN;
UPDATE accounts SET balance = balance - 200 WHERE user_id = 2; -- Блокирует строку user_id=2
-- Транзакция B теперь ждет блокировку на user_id=1...
-- ВОЗНИКАЕТ DEADLOCK

Как с этим справляется СУБД:

  1. Детектор взаимоблокировок периодически проверяет граф ожидания транзакций.
  2. При обнаружении цикла СУБД принудительно откатывает (ROLLBACK) одну из транзакций (обычно ту, которую дешевле откатить, или которая пришла позже). Эта транзакция получает ошибку (например, ERROR: deadlock detected).
  3. Оставшиеся транзакции могут продолжить выполнение.

Методы предотвращения:

  • Упорядоченный доступ: Всегда блокировать ресурсы (например, строки таблицы) в одном и том же порядке (по возрастанию ID).
  • Короткие транзакции: Минимизировать время удержания блокировок.
  • Использование таймаутов: SET lock_timeout = '2s'; в PostgreSQL или настройки уровня изоляции.
  • Оптимистичные блокировки: Использование версий строк (UPDATE ... WHERE id=1 AND version=5) вместо долгих пессимистичных блокировок.

Ответ 18+ 🔞

А, deadlock! Ну это классика, ёпта, как в очереди за водкой — ты стоишь за мужиком, а он ждёт, пока его тёща отслюнявит мелочь из кошелька, а та, сука, ждёт, пока кассирша сдачу даст. И все стоят, упершись лбами, нихуя не двигаются. Вот это и есть взаимная блокировка, только в базе данных.

Представь себе, две транзакции, как два упёртых мужика в баре:

-- Мужик А хватает бутылку виски (user_id=1) и тянется за пивом (user_id=2)
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;

-- Мужик Б в это время хватает как раз это пиво (user_id=2) и хочет вискаря (user_id=1)
BEGIN;
UPDATE accounts SET balance = balance - 200 WHERE user_id = 2;

И всё, приехали. Каждый держит в одной руке то, что нужно другому, и ждёт, пока тот отпустит. Висят, блядь, как два идиота, и работа дальше не едет. Удивление пиздец, но система-то не тупая.

Как база данных эту поебень разгребает:

  1. У неё там есть такой детектор, который, как участковый, смотрит — а чё это вы, суки, друг на друга упёрлись и циклитесь? Видит петлю — всё, волнение ебать, терпения ноль.
  2. База берёт и одного из мудаков вышвыривает на мороз (делает ROLLBACK). Обычно того, кто позже пришёл или кому меньше работы откатывать. Вылетает он с позором и ошибкой ERROR: deadlock detected. Второй, довольный такой, продолжает пить.
  3. Остальные транзакции, которые не вляпались, работают дальше.

Чтобы самому не попасть в такую историю, где доверия ебать ноль, есть правила:

  • Упорядоченный доступ, как в армии. Всегда блокируй строки в одном и том же порядке, например, по возрастанию ID. Сначала бутылка №1, потом №2. Тогда второй мужик просто подождёт в очереди, а не устроит бардак.
  • Короткие транзакции. Не растягивай удовольствие, как последний алкаш. Схватил — обновил — отпустил — пошёл дальше. Чем дольше держишь блокировку, тем больше шансов, что кто-то в тебя врежется.
  • Таймауты. Скажи системе: «Слушай, если я больше двух секунд жду — бросай это дело, ёб твою мать». В Постгресе это SET lock_timeout = '2s';.
  • Оптимистичные блокировки. Это как не хватать бутылку, а сказать: «Я беру вот эту, пятую по счёту». А если пока шёл, её уже кто-то унёс (версия изменилась) — ну, извини, братан, иди за новой. Меньше мороки, но нужно переделывать операцию, если не повезло.

В общем, deadlock — это не конец света, система его обычно ловит и убивает. Но лучше жить так, чтобы не доводить до такого, а то ведь можно и впиздюрить себе по производительности.