Что такое дедлок в InnoDB
Дедлок — это взаимная блокировка: транзакция A ждёт освобождения строки, занятой транзакцией B, а транзакция B в этот момент ждёт строку, занятую транзакцией A. Ни одна из них не может продолжить работу, и InnoDB через встроенный детектор сам обрывает одну из транзакций с ошибкой Deadlock found when trying to get lock. Это не сбой сервера, а штатный механизм защиты от зависания.
В отличие от обычной блокировки, которая просто заставляет транзакцию подождать, дедлок требует вмешательства приложения: код должен поймать ошибку и повторить транзакцию заново.
Разделяемые и монопольные блокировки строк
InnoDB блокирует не таблицу целиком, а отдельные строки и промежутки между ними. Понимание типов блокировок помогает читать вывод диагностики.
- Shared lock (S) — разделяемая блокировка, ставится при SELECT ... FOR SHARE, разрешает другим транзакциям тоже читать строку.
- Exclusive lock (X) — монопольная блокировка, ставится при UPDATE, DELETE и SELECT ... FOR UPDATE, запрещает любой доступ другим транзакциям.
- Gap lock — блокировка промежутка между значениями индекса, нужна для предотвращения фантомных строк при повторяемом чтении.
Как найти последний дедлок на сервере
Информация о последнем зафиксированном дедлоке хранится в статусе движка InnoDB. Команда выводит обе транзакции, их блокировки и то, какую из них сервер откатил.
SHOW ENGINE INNODB STATUS\GВ блоке LATEST DETECTED DEADLOCK видно текст запроса каждой транзакции, список удерживаемых и ожидаемых блокировок, а в конце — строку WE ROLL BACK TRANSACTION с номером отменённой транзакции. Разбор плана этих запросов через EXPLAIN часто показывает, что оба запроса идут по разным индексам к одним и тем же строкам.
Таблица типовых причин дедлоков
| Причина | Пример | Что делать |
|---|---|---|
| Разный порядок блокировок | Транзакция A обновляет строки 1→2, транзакция B — 2→1 | Зафиксировать единый порядок изменения строк |
| Долгая транзакция | Открытая транзакция ждёт ответа внешнего сервиса | Не держать транзакцию открытой во время сетевых вызовов |
| Отсутствие индекса | UPDATE идёт по неиндексированному полю | Добавить индекс, чтобы блокировался диапазон меньше |
Частые причины дедлоков в реальных приложениях
На практике большинство дедлоков повторяются по одному и тому же сценарию.
- Два обработчика заказа одновременно списывают остаток товара в разном порядке строк.
- Триггер на таблице делает вставку в другую таблицу, создавая скрытую вторую блокировку.
- Массовое обновление без сортировки строк по первичному ключу — порядок блокировок становится случайным.
- Долгая транзакция удерживает блокировку, пока приложение ждёт ответа от очереди или внешнего API.
Как снизить число дедлоков
Полностью убрать дедлоки нельзя — это часть нормальной работы MySQL под нагрузкой, но их частоту снижают несколько приёмов.
-- Сортировка строк по первичному ключу перед изменением
SELECT id FROM orders WHERE status = 'new' ORDER BY id FOR UPDATE;- Обновлять строки в одном и том же порядке во всех местах кода.
- Держать транзакции короткими: не выполнять внешние вызовы между BEGIN и COMMIT.
- Добавлять индексы под условия WHERE в UPDATE и DELETE, чтобы блокировался только нужный диапазон.
- Повторять транзакцию автоматически при ошибке 1213 (deadlock), обычно двух-трёх попыток достаточно.
Итог: чек-лист диагностики дедлоков
- Прочитать LATEST DETECTED DEADLOCK в SHOW ENGINE INNODB STATUS.
- Определить порядок блокировок в обеих транзакциях.
- Проверить, есть ли подходящий индекс под условия запросов.
- Сократить транзакцию до минимально необходимых операций.
- Добавить в код повтор транзакции при ошибке 1213.