Взаимоблокировка возникает, когда у вас есть 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”