Взаимные блокировки (Deadlock).
Взаимная блокировка или deadlock – ситуация в многопользовательской СУБД, когда две различные транзакции находятся в состоянии бесконечного ожидания ресурсов, занятых этими же транзакциями. InnoDB обнаруживает такие взаимные блокировки и прерывает одну из транзакций, чтобы дать завершиться другой транзакции.
Deadlock можно назвать классической проблемой транзакционных баз данных. Это, даже не проблема, а просто рабочий момент и не повод для беспокойства. Поводом для беспокойства могут стать слишком частые deadlock-и, в результате чего транзакции не могут нормально выполняться, и работа приложения может остановиться. Как правило, в случае возникновения такой ситуации следует обратиться к программистам, поддерживающих данное приложение.
Основные причины возникновения deadlock-ов:
- Транзакции накладывают блокировки на одинаковые таблицы, но в обратном порядке.
- команды UPDATE и SELECT…FOR UPDATE в различных транзакциях на первом шаге устанавливают блокировку каких-либо данных и затем вторым шагом пытаются выставить следующую блокировку. При этом первая транзакция пытается заблокировать данные уже заблокированные второй транзакцией перед этим, а вторая транзакция наоборот пытается выставить блокировку на данные, уже заблокированые в первой транзакции.
- Несколько транзакций ждут друг друга по кругу. Например, транзакция Т1 ждет транзакцию Т2, Т2 ждет Т3, а Т3 – Т1.
Чтобы уменьшить вероятность возникновения взаимных блокировок разнаботчики MySQL рекомендуют:
- Чаще выполнять COMMIT, проверять в приложении ошибки и в случае обнаружения deadlock проводить заново откатившуюся транзакцию.
- Использовать небольшие транзакции (т.е. небольшое количество строк вставляется, удаляется или изменяется). В первую очередь, чтобы избежать откатов (rollback) больших изменений.
- Осуществлять доступ к таблицам и строкам в определенном порядке.
- Добавить хорошо продуманные индексы на таблицы.
- Использовать меньше блокировок или устанавливать ниже уровень изоляции транзакций (например, READ COMMITTED).
Для того, чтобы понять причины взаимных блокировок можно использовать:
- Команду SHOW ENGINE INNODB STATUS, которая выдаст информацию по самому последнему dtadlock-у.
- перезапустить MySQL сервер c включенным параметром innodb_print_all_deadlocks (начиная с версии 5.5.30). В этом случае информация о всех deadlock-ах будет попадать в error.log.
Следует отметить еще одну особенность отката транзакций при возникновении deadlock-ов. Когда InnoDB осуществляет откат текущей транзакции, все блокировки освобождаются. Однако это касается простых SQL команд. Некоторые блокировки могут все-таки остаться. Например, если в команде SELECT вызывается какая-то функция, в которой устанавливаются блокировки, а после этой команды SELECT случается deadlock и откат транзакции.
И в заключении классический пример, как можно получить deadlock:
Сессия 1 | Сессия 2 |
s1> START TRANSACTION;S1> UPDATE PERSONS SET LAST_NAME = ‘IVANOV’ WHERE ID=5; |
|
s2> START TRANSACTION;s2> UPDATE PERSONS SET LAST_NAME = ‘PETROV’ WHERE ID=7; |
|
s1> DELETE FROM PERSONS WHERE LAST_NAME = ‘PETROV’; | |
s2>UPDATE PERSONS SET FIRST_NAME = ‘STEPAN’ WHERE ID=5;ERROR 1213 (40001) : Deadlock found when trying to get lock; try restarting transaction |
|
Query OK, 1 row affected (0.0 sec) |