Как избежать mysql 'Deadlock найден при попытке получить блокировку; попробуйте перезапустить транзакцию

204

У меня есть таблица innoDB, в которой записываются онлайн-пользователи. Он обновляется на каждой странице, обновляемой пользователем, чтобы отслеживать, на каких страницах они находятся, и дату их последнего доступа на сайт. Затем у меня есть cron, который запускается каждые 15 минут, чтобы УДАЛИТЬ старые записи.

У меня появился "Тупик", который пытался найти замок; попробуйте перезапустить транзакцию "около 5 минут прошлой ночью, и, похоже, при запуске INSERT в эту таблицу. Может кто-нибудь предложить, как избежать этой ошибки?

=== РЕДАКТИРОВАТЬ ===

Вот выполняемые запросы:

Первый визит на сайт:

INSERT INTO onlineusers SET
ip = 123.456.789.123,
datetime = now(),
userid = 321,
page = '/thispage',
area = 'thisarea',
type = 3

На каждом обновлении страницы:

UPDATE onlineusers SET
ips = 123.456.789.123,
datetime = now(),
userid = 321,
page = '/thispage',
area = 'thisarea',
type = 3
WHERE id = 888

Cron каждые 15 минут:

DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND

Затем происходит подсчет некоторых статистических данных (например, участники онлайн, посетители в Интернете).

  • 0
    Можете ли вы предоставить более подробную информацию о структуре таблицы? Есть ли кластерные или некластеризованные индексы?
  • 9
    dev.mysql.com/doc/refman/5.1/en/innodb-deadlocks.html - Запуск «show innodb status» предоставит полезную диагностику.
Теги:
deadlock

6 ответов

235
Лучший ответ

Один простой трюк, который может помочь с большинством взаимоблокировок, - это сортировка операций в определенном порядке.

Вы получаете тупик, когда две транзакции пытаются заблокировать две блокировки с противоположными порядками, то есть:

  • соединение 1: клавиша блокировки (1), клавиша блокировки (2);
  • соединение 2: клавиша блокировки (2), клавиша блокировки (1);

Если оба запускаются одновременно, соединение 1 блокирует ключ (1), соединение 2 блокирует ключ (2), и каждое соединение будет ждать, пока другой отпустит ключ → тупик.

Теперь, если вы изменили свои запросы таким образом, чтобы соединения блокировали ключи в том же порядке, то есть:

  • соединение 1: клавиша блокировки (1), клавиша блокировки (2);
  • соединение 2: клавиша блокировки (1), клавиша блокировки ( 2);

невозможно будет зайти в тупик.

Так вот что я предлагаю:

  • Убедитесь, что у вас нет других запросов, которые блокируют доступ к нескольким ключевым словам за раз, за ​​исключением инструкции удаления. если вы это сделаете (и я подозреваю, что вы это сделали), закажите их ГДЕ в (k1, k2,.. kn) в порядке возрастания.

  • Исправьте оператор delete для работы в порядке возрастания:

Измените

DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND

Для

DELETE FROM onlineusers WHERE id IN (SELECT id FROM onlineusers
    WHERE datetime <= now() - INTERVAL 900 SECOND order by id) u;

Еще одна вещь, о которой следует помнить, заключается в том, что документация mysql предполагает, что в случае тупика клиент должен повторить попытку автоматически. вы можете добавить эту логику в свой код клиента. (Скажем, 3 попытки повторить эту ошибку перед тем, как сдаться).

  • 2
    Если у меня есть Transaction (autocommit = false), генерируется исключение взаимоблокировки. Достаточно ли просто повторить тот же оператор Statement.executeUpdate (), или вся транзакция теперь отображается и должна быть откатана + перезапустить все, что в ней выполнялось?
  • 3
    если у вас включены транзакции, это все или ничего. если у вас есть какое-либо исключение, это гарантирует, что вся транзакция не имела никакого эффекта. в этом случае вы захотите перезапустить все это.
Показать ещё 6 комментариев
55

Тупик случается, когда две транзакции ожидают друг друга, чтобы получить блокировку. Пример:

  • Tx 1: блокировка A, затем B
  • Tx 2: заблокировать B, затем A

Существует множество вопросов и ответов о взаимоблокировках. Каждый раз, когда вы вставляете/обновляете/или удаляете строку, происходит блокировка. Чтобы избежать тупиковой ситуации, вы должны убедиться, что параллельные транзакции не обновляют строку в порядке, который может привести к тупиковой ситуации. Вообще говоря, попытайтесь получить блокировку всегда в том же порядке даже в разных транзакциях (например, всегда сначала таблицу A, затем таблицу B).

Другой причиной тупика в базе данных может быть отсутствующих индексов. Когда строка вставлена ​​/обновлена ​​/удалена, база данных должна проверять реляционные ограничения, то есть убедиться, что отношения согласованы. Для этого база данных должна проверять внешние ключи в связанных таблицах. Это может привести к приобретению другой блокировки, чем измененная строка. Убедитесь, что всегда есть индекс для внешних ключей (и, конечно, первичных ключей), иначе это может привести к блокировке таблицы вместо блокировки строк . Если происходит блокировка таблицы, конфликт блокировки выше и вероятность блокировки увеличивается.

  • 3
    Так что, возможно, моя проблема в том, что Пользователь обновил страницу и, таким образом, вызвал ОБНОВЛЕНИЕ записи, в то время как cron пытается выполнить УДАЛЕНИЕ для записи. Однако я получаю сообщение об ошибке на INSERTS, поэтому cron не будет УДАЛИТЬ записи, которые только что были созданы. Так как же может возникнуть тупик на записи, которая еще не вставлена?
  • 0
    Можете ли вы предоставить немного больше информации о таблицах и что именно делают транзакции?
Показать ещё 3 комментария
6

Вполне вероятно, что оператор delete будет влиять на большую часть общих строк в таблице. В конечном итоге это может привести к блокировке таблицы при удалении. Удержание блокировки (в данном случае row- или блокировки страниц) и получение большего количества блокировок всегда является фактором взаимоблокировки. Однако я не могу объяснить, почему оператор insert приводит к эскалации блокировки - возможно, это связано с разбиением/добавлением страницы, но кто-то, кто знает MySQL, должен будет заполнить его.

Для начала можно попытаться явно получить блокировку таблицы сразу для инструкции delete. См. ЛОКАЛЬНЫЕ ТАБЛИЦЫ и проблемы с блокировкой таблиц.

5

Вы можете попробовать, чтобы это задание на delete работало, сначала вставив ключ каждой строки, которая будет удалена в временную таблицу, такую как псевдокод

create temporary table deletetemp (userid int);

insert into deletetemp (userid)
  select userid from onlineusers where datetime <= now - interval 900 second;

delete from onlineusers where userid in (select userid from deletetemp);

Прерывание этого, как это, менее эффективно, но это позволяет избежать блокировки ключа при delete.

Кроме того, измените ваши запросы select чтобы добавить предложение where, исключая строки старше 900 секунд. Это позволяет избежать зависимости от задания cron и позволяет перенастроить его на работу менее часто.

Теория о взаимоблокировках: у меня нет большого фона в MySQL, но здесь идет... delete будет содержать блокировку ключевого диапазона для datetime, чтобы предотвратить соответствие строк, where предложение where добавлено в середине транзакция, и поскольку она находит строки для удаления, она попытается получить блокировку на каждой странице, которую она модифицирует. insert собирается получить блокировку на странице, в которую она вставляется, а затем попытаться получить блокировку ключа. Обычно insert будет терпеливо ждать, пока эта блокировка клавиатуры не откроется, но это затормозит, если delete попытается заблокировать ту же страницу, которую использует insert потому что для delete требуется эта блокировка страницы, а insert нуждается в блокировке ключа. Это не похоже на вставки, но delete и insert используют диапазоны datetime, которые не перекрываются, поэтому, возможно, что-то еще происходит.

http://dev.mysql.com/doc/refman/5.1/en/innodb-next-key-locking.html

1

Для программистов на Java, использующих Spring, я избежал этой проблемы, используя аспект AOP, который автоматически повторяет транзакции, которые запускаются в переходные тупики.

См. @RetryTransaction Javadoc для получения дополнительной информации.

  • 0
    Вы можете обновить свою ссылку? он мертв
  • 0
    Обновлено - спасибо!
0

У меня есть метод, внутренности которого завернуты в MySqlTransaction.

Вопрос о тупике появился для меня, когда я запускал тот же метод параллельно с собой.

Не было проблемы с запуском одного экземпляра метода.

Когда я удалил MySqlTransaction, я смог запустить этот метод параллельно с собой без проблем.

Просто поделившись своим опытом, я ничего не пропагандирую.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню