Как избежать mysql 'Deadlock найден при попытке получить блокировку; попробуйте перезапустить транзакцию - PullRequest
242 голосов
/ 25 февраля 2010

У меня есть таблица 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

Затем он делает некоторые подсчеты, чтобы регистрировать некоторые статистические данные (то есть: пользователи онлайн, посетители онлайн).

Ответы [ 7 ]

253 голосов
/ 11 марта 2010

Одним из простых приемов, которые могут помочь при большинстве взаимоблокировок, является сортировка операций в определенном порядке.

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

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

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

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

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

получить тупик невозможно.

Вот что я предлагаю:

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

  2. Исправьте оператор удаления, чтобы он работал в порядке возрастания:

Измените

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 повторения этой конкретной ошибки, прежде чем отказаться).

65 голосов
/ 25 февраля 2010

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

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

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

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

9 голосов
/ 06 марта 2010

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

Для начала стоит попробовать явно получить блокировку таблицы сразу для оператора delete. См. LOCK TABLES и Проблемы блокировки таблиц .

6 голосов
/ 11 марта 2010

Вы можете попробовать запустить это задание 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 собирается удерживать блокировку диапазона ключей для даты и времени, чтобы предотвратить совпадение строк с предложением where от добавления в середине транзакции и поиска строк для удаления, она будет пытаться получить блокировку на каждой странице, которую она изменяет. insert собирается получить блокировку на странице, в которую он вставляет, и затем пытается получить блокировку ключа. Обычно insert будет терпеливо ждать, пока откроется блокировка клавиш, но это приведет к взаимоблокировке, если delete попытается заблокировать ту же страницу, которую использует insert, потому что delete нужна эта блокировка страницы и insert нужен этот замок с ключом. Это не подходит для вставок, хотя delete и insert используют диапазоны даты и времени, которые не перекрываются, поэтому возможно что-то еще происходит.

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

3 голосов
/ 29 июня 2013

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

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

1 голос
/ 26 февраля 2019

В случае, если кто-то все еще борется с этой проблемой:

Я столкнулся с подобной проблемой, когда 2 запроса одновременно попадали на сервер. Не было ситуации, как показано ниже:

T1:
    BEGIN TRANSACTION
    INSERT TABLE A
    INSERT TABLE B
    END TRANSACTION

T2:
    BEGIN TRANSACTION
    INSERT TABLE B
    INSERT TABLE A
    END TRANSACTION

Итак, я был озадачен, почему происходит тупик.

Затем я обнаружил, что между двумя таблицами был родительско-дочерний отношения из-за внешнего ключа. Когда я вставлял запись в дочернюю таблицу, транзакция получала блокировку строки родительской таблицы. Сразу после этого я пытался обновить родительскую строку, которая вызывала повышение блокировки, до ИСКЛЮЧИТЕЛЬНОЙ. Поскольку вторая параллельная транзакция уже удерживала блокировку SHARED, она вызывала тупик.

См .: https://blog.tekenlight.com/2019/02/21/database-deadlock-mysql.html

0 голосов
/ 04 августа 2018

У меня есть метод, внутренняя часть которого обернута в MySqlTransaction.

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...