Що таке дедлок в 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.