Рубрики
Без рубрики

Решение тупиковой ситуации в MySQL

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

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

Представьте, что у нас есть 2 потока в приложении Java:

  • Поток 1 получает блокировку ресурса A
  • Поток 2 получает блокировку ресурса B
  • Для того, чтобы продолжить выполнение (и снять блокировку с ресурса A) , Поток 1 ожидает, пока ресурс B не освободится
  • Чтобы продолжить выполнение (и снять блокировку с ресурса B), поток 2 ожидает, пока ресурс A не освободится

Ни один из потоков никогда не завершит выполнение, и наше приложение зашло в тупик.

Вы действительно можете попробовать это для себя в таблице MySQL:

mysql> CREATE TABLE a (id int primary key, value int);
Query OK, 0 rows affected (0.01 sec)

mysql> INSERT INTO a VALUES (1, 0), (2, 0), (3, 0), (4, 0);
Query OK, 4 rows affected (0.01 sec)
Records: 4  Duplicates: 0  Warnings: 0

mysql console 1:
step 1> START TRANSACTION;
step 3> UPDATE a SET value = 1 WHERE id = 2;
step 5> UPDATE a SET value = 1 WHERE id = 1;

mysql console 2:
step 2> START TRANSACTION;
step 4> UPDATE a SET value = 1 WHERE id = 1;
step 6> UPDATE a SET value = 1 WHERE id = 2;

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

Недавно я работал над многопоточным приложением, у которого была эта проблема. Это было Java-приложение с 16 потоками. Каждый поток будет обновлять пакет из 10 строк в таблице MySQL каждые 1 мс. При обновлении строки в MySQL для этой конкретной строки устанавливается эксклюзивная блокировка, блокирующая обновление любой другой транзакции. Это случалось не часто, но иногда 2 потока ждали друг друга, чтобы освободить блокировку строк, что приводило к тупику.

Я рассмотрел несколько различных решений:

  • Перезапуск транзакции в случае возникновения взаимоблокировки. Проблема: я бы обновлял свои строки устаревшими данными
  • Перезапуск всего процесса в случае возникновения тупиковой ситуации. Проблема: Это потенциально может сделать приложение намного медленнее.
  • Сделайте так, чтобы каждый поток “утверждал” строку, прежде чем пытаться ее обновить. Если он уже заявлен, найдите другую строку для обновления. Проблема: Это значительно усложняет приложение и также снижает производительность

Прежде чем продолжить, я сделал перерыв и еще раз подумал о причине тупика. Все потоки выполняли по 10 обновлений каждый в одной и той же таблице MySQL. Единственный способ, которым может возникнуть тупик, – это если порядок и время соответствуют очень определенному шаблону. Я не мог контролировать время, но я мог контролировать порядок обновлений.

На самом деле, если бы я заказал все обновления одинаково (например, в порядке возрастания идентификатора строки), это исключило бы возможность взаимоблокировки.

Но почему обновления все равно происходят в случайном порядке? Оказывается, приложение извлекает 100 строк из базы данных и выбирает 10 для обновления. Однако, чтобы 16 потоков не могли всегда обновлять одни и те же 10 строк, сначала необходимо перетасовать список из 100 строк. Это означает, что 10 обновлений всегда будут происходить в некотором случайном порядке.

Я изменил приложение так, чтобы перед обновлением 10 строк оно упорядочивало обновления в порядке возрастания строки идентификатор. Я развернул приложение, и на этом тупики закончились.

Тупики могут возникать по-разному, но в данном случае извлеченный урок заключается в следующем:

Если вы ищете ресурсы в многопоточном приложении, убедитесь, что все потоки блокируются в одном и том же порядке (порядок основан на атрибуте ресурса).

В более простых терминах:

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

Оригинал: “https://dev.to/seymour7/solving-a-mysql-deadlock-bpm”