У меня есть таблица 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
Крон каждые 15 минут:
DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND
Затем он делает некоторые подсчеты, чтобы регистрировать некоторые статистические данные (то есть: пользователи онлайн, посетители онлайн).
Ответы:
Одним из простых приемов, которые могут помочь при большинстве взаимоблокировок, является сортировка операций в определенном порядке.
Вы получаете тупик, когда две транзакции пытаются заблокировать две блокировки в противоположных порядках, то есть:
Если оба запускаются одновременно, соединение 1 заблокирует ключ (1), соединение 2 заблокирует ключ (2), и каждое соединение будет ожидать, пока другое не отпустит ключ -> тупик.
Теперь, если вы изменили свои запросы так, чтобы соединения блокировали ключи в том же порядке, то есть:
невозможно будет зайти в тупик.
Вот что я предлагаю:
Убедитесь, что у вас нет других запросов, которые блокируют доступ более чем к одному ключу одновременно, кроме оператора delete. если вы делаете (и я подозреваю, что вы делаете), закажите их ГДЕ в (k1, k2, .. kn) в порядке возрастания.
Исправьте оператор удаления для работы в порядке возрастания:
+ Изменить
к
Следует также помнить, что документация mysql предполагает, что в случае тупика клиент должен повторить попытку автоматически. Вы можете добавить эту логику в свой клиентский код. (Скажем, 3 повторения этой конкретной ошибки, прежде чем сдаться).
источник
Тупик возникает, когда две транзакции ждут друг друга, чтобы получить блокировку. Пример:
Есть множество вопросов и ответов о тупиках. Каждый раз, когда вы вставляете / обновляете / или удаляете строку, блокировка получается. Чтобы избежать взаимоблокировки, необходимо убедиться, что параллельные транзакции не обновляют строки в порядке, который может привести к взаимоблокировке. Вообще говоря, попытайтесь получить блокировку всегда в одном и том же порядке даже в разных транзакциях (например, всегда сначала таблица A, затем таблица B).
Еще одной причиной тупиковой ситуации в базе данных могут быть отсутствующие индексы . Когда строка вставляется / обновляется / удаляется, базе данных необходимо проверить реляционные ограничения, то есть убедиться, что отношения согласованы. Для этого базе данных необходимо проверить внешние ключи в связанных таблицах. Это может привести к получению другой блокировки, кроме измененной строки. Тогда убедитесь, что всегда есть индекс для внешних ключей (и, конечно, первичных ключей), иначе это может привести к блокировке таблицы вместо блокировки строки . Если блокировка таблицы происходит, конкуренция за блокировку выше, и вероятность взаимоблокировки увеличивается.
источник
Вполне вероятно, что оператор удаления повлияет на большую часть всех строк в таблице. В конечном итоге это может привести к блокировке таблицы при удалении. Удержание блокировки (в данном случае блокировка строк или страниц) и получение дополнительных блокировок - всегда риск тупиковой ситуации. Однако я не могу объяснить, почему оператор вставки приводит к эскалации блокировки - это может быть связано с разделением / добавлением страниц, но кто-то, знающий MySQL лучше, должен будет заполнить его.
Для начала стоит попробовать явно получить блокировку таблицы сразу для оператора delete. См. LOCK TABLES и Проблемы с блокировкой таблиц .
источник
Вы можете попробовать
delete
запустить это задание, сначала вставив ключ каждой строки, которую нужно удалить, во временную таблицу, подобную этому псевдокоду.Такое разбиение менее эффективно, но позволяет избежать необходимости удерживать блокировку диапазона клавиш во время
delete
.Кроме того, измените ваши
select
запросы, чтобы добавитьwhere
предложение, исключая строки старше 900 секунд. Это позволяет избежать зависимости от задания cron и позволяет перепланировать его запуск менее часто.Теория о взаимоблокировках: у меня нет большого опыта в MySQL, но здесь идет речь ... Будет
delete
удерживаться блокировка диапазона ключей для datetime, чтобы предотвратитьwhere
добавление строк, соответствующих его предложению, в середине транзакции и, поскольку он находит строки для удаления, он пытается установить блокировку на каждой странице, которую он изменяет. Онinsert
собирается получить блокировку на странице, в которую он вставляет, и затем попытается получить блокировку ключа. Обычно онinsert
будет терпеливо ждать, пока откроется блокировка клавиатуры, но это приведет к взаимоблокировке, еслиdelete
попытка заблокировать ту же страницу, чтоinsert
и используется,delete
требует, чтобы блокировка страницы былаinsert
необходима, а блокировка ключа - необходима. Это не подходит для вставок, хотяdelete
иinsert
используют диапазоны даты и времени, которые не перекрываются, так что, возможно, происходит что-то еще.http://dev.mysql.com/doc/refman/5.1/en/innodb-next-key-locking.html
источник
В случае, если кто-то все еще борется с этой проблемой:
Я столкнулся с подобной проблемой, когда 2 запроса одновременно попадали на сервер. Там не было ситуации, как показано ниже:
Итак, я был озадачен, почему происходит тупик.
Затем я обнаружил, что между двумя таблицами был родительско-дочерний корабль связи из-за внешнего ключа. Когда я вставлял запись в дочернюю таблицу, транзакция получала блокировку строки родительской таблицы. Сразу после этого я пытался обновить родительскую строку, которая вызывала повышение блокировки, до ИСКЛЮЧИТЕЛЬНОЙ. Поскольку 2-я параллельная транзакция уже удерживала блокировку SHARED, она вызывала тупик.
См. Https://blog.tekenlight.com/2019/02/21/database-deadlock-mysql.html.
источник
Для Java-программистов, использующих Spring, я избежал этой проблемы, используя аспект AOP, который автоматически повторяет транзакции, которые переходят во временные тупики.
Смотрите @RetryTransaction Javadoc для получения дополнительной информации.
источник
У меня есть метод, внутренности которого обернуты в MySqlTransaction.
Проблема тупика обнаружилась, когда я запустил один и тот же метод параллельно с самим собой.
Не было проблемы с запуском одного экземпляра метода.
Когда я удалил MySqlTransaction, я смог запустить метод параллельно с самим собой без проблем.
Просто делюсь своим опытом, я ничего не защищаю.
источник
cron
опасный. Если один экземпляр cron не завершит работу до следующего, они, скорее всего, будут сражаться друг с другом.Было бы лучше иметь постоянно работающую работу, которая удаляла бы некоторые строки, спала некоторые, а затем повторялась.
Также
INDEX(datetime)
очень важно избегать тупиков.Но если проверка даты и времени включает в себя более, скажем, 20% таблицы,
DELETE
выполняется сканирование таблицы. Меньшие куски удаляются чаще, это обходной путь.Другая причина для перехода с меньшими кусками - это блокировка меньшего количества строк.
Нижняя граница:
INDEX(datetime)
Другие методы удаления: http://mysql.rjweb.org/doc.php/deletebig
источник