Ответ
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, чтобы не ждать заблокированную строку, а сразу получить ошибку или пропустить её.
Ответ 18+ 🔞
Ну вот, сидишь ты такой, пишешь свой код, всё летает, а потом — бац! — и всё встало колом. Это, дружище, не глюк, это классический deadlock, или, по-нашему, взаимная блокировка. Представь себе двух упырей в узком коридоре, которые упираются друг в друга лбами и орут: «Ты первый посторонись!» — «Нет, ты!». И оба никуда не двигаются, пока один не сдастся. В базах данных — та же самая песня, только вместо упырей — транзакции.
Как это выглядит в жизни:
Одна транзакция схватила за горло одну запись, вторая — другую. А потом каждая из них тянет лапу к записи, которую уже держит соперник. И всё, пиши пропало — патовая ситуация.
-- Транзакция 1 начинает свою авантюру
BEGIN TRANSACTION;
-- Она ловко прихватывает запись с id = 1
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- А теперь пытается взять id = 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, который уже в цепких лапах первой транзакции
UPDATE accounts SET balance = balance + 50 WHERE id = 1;
COMMIT;
Вот и получился цирк: первая ждёт вторую, вторая — первую. Терпения ноль ебать!
Как из этого дерьма вылезает сама СУБД?
Она не дура, у неё внутри есть детектор таких идиотских ситуаций. Она строит граф, видит петлю и говорит: «Всё, ребята, разборки окончены». Одну из транзакций (обычно ту, что меньше наворотила) она приканчивает — делает ROLLBACK и шлёт ошибку в приложение. Остальная работает дальше. Жестоко, но справедливо.
А что делать нам, чтобы не попадать в такие просак?
- Порядок, блядь, порядок! Всегда блокируй ресурсы в одной и той же последовательности. Если во всём приложении сначала
id = 1, потомid = 2, то deadlock просто не сможет возникнуть. Это как очередь в столовой — все идут к одному котлу, а не дерутся за два. - Быстрее, сука, быстрее! Делай транзакции короткими. Пришёл, сделал дело, отпустил блокировки — не сиди там, как будто тебе дома нечего делать.
- Не усложняй. Не используй уровень изоляции
SERIALIZABLE, если без него можно обойтись. Он создаёт овердохуища блокировок. - Не жди у моря погоды. Используй
SELECT ... FOR UPDATE NOWAIT. Если запись уже заблокирована, ты сразу получишь ошибку, а не будешь висеть в ожидании, как лох. ИлиSKIP LOCKED— просто проигнорируй заблокированное и иди дальше.
Короче, deadlock — это не конец света. Это просто пинок под зад, чтобы ты писал код умнее. СУБД страхует, но лучше до такого не доводить, а то волнение ебать потом, когда продакшн встанет.