Взаимоблокировки - классическая проблема транзакционных баз данных. Они не опасны до тех пор пока не становятся настолько частыми, что вы вообще не можете запустить некоторые транзакции. Обычно вы можете написать свои приложения таким образом, что они всегда будут подготавливать перезапуск транзакции, если произошел откат из-за взаимоблокировок.
InnoDB использует автоматическую блокировку уровня строки. Вы можете создать взаимоблокировку даже в случае транзакций, которые всего лишь добавляют или удаляют единичную строку. Это происходит из-за того, что в действительности эти операции не являются "атомарными": они автоматически устанавливают блокировку на индексные записи добавляемых/удаляемых строк (или на несколько записей).
Вы можете избежать взаимоблокировок или уменьшить их количество, следуя следующим приемам:
Используйте
SHOW INNODB STATUS
в MySQL начиная с 3.23.52 и 4.0.3 для определения причины последней взаимоблокировки. Это поможет вам настроить ваше приложение, что бы избежать взаимоблокировок.Всегда подготавливайте перезапуск транзакции, если произошел откат из-за взаимоблокировки. Взаимоблокировка не опасна: всего лишь попробуйте еще раз.
Чаще фиксируйте свои транзакии. Маленькие транзакции меньше склонны к противоречиям.
Если вы используете чтение с блокировкой
SELECT ... FOR UPDATE
или... LOCK IN SHARE MODE
, попробуйте использовать более низкий уровень изоляцииREAD COMMITTED
.Производите операции с вашими таблицам и строками в фиксированном порядке. Тогда транзакции будут формировать очередь и не будет происходить взаимоблокировка.
Добавьте хорошие индексы на ваши таблицы. Тогда ваши запросы будут сканировать меньше индексных записей и, соответственно, будут устанавливать меньше блокировок. Используйте
EXPLAIN SELECT
для того, чтобы узнать, выбирает ли MySQL соответствующий индекс для ваших запросов.Используйте меньше блокировок: если вы можете допустить, чтобы
SELECT
возвращал данные из старого снимка, не добавляйте к выражениюFOR UPDATE
илиLOCK IN SHARE MODE
. Используйте уровень изоляцииREAD COMMITTED
, который больше всего подходит для данной ситуации, так как каждое согласованное чтение внутри одной и той же транзакции читает свой собственный свежий снимок.Если ничего не помогло, сериализируйте свои транзакции с блокировкой уровня таблиц:
LOCK TABLES t1 WRITE, t2 READ, ... ; [здесь можете развлекаться с таблицами t1 и t2]; UNLOCK TABLES
. Блокировка на уровне таблиц выстраивает ваши транзакции в очередь, и позволяет избежать взаимоблокировки. Заметьте, чтоLOCK TABLES
неявным образом начинает транзакцию наподобиеBEGIN
, иUNLOCK TABLES
неявным образом завершает ее вCOMMIT
.Другое решение для сериализации транзакций - это создание вспомагательного "семафора" таблицы, где есть всего лишь одна строка. Каждая транзакция обновляет эту строку перед доступом к другой таблице. В этом случае все транзакции выполняются в виде очереди. Отметим что таким же образом в настоящий момент работает и алгоритм определения взаимоблокировок в InnoDB, так как блокировка сериализации - это блокировка уровня строки. При блокировке на уровне таблицы в MySQL мы используем метод таймаута для разрешения взаимоблокировки.