MySQL Deadlock, используя индекс с новым значением - PullRequest
0 голосов
/ 04 июня 2019

Таблица:

create table properties
(
  id              int auto_increment primary key,
  other_id        int          null
);

create index index_properties_on_other_id
  on properties (other_id);

TX 1:

start transaction;
SET @last_id = 1;
delete from `properties` WHERE `properties`.`other_id` = @last_id;
INSERT INTO `properties` (`other_id`) VALUES (@last_id);
commit

TX 2:

start transaction;
SET @last_id = 2;
delete from `properties` WHERE `properties`.`other_id` = @last_id;
INSERT INTO `properties` (`other_id`) VALUES (@last_id);
commit

Предположим, что таблица пуста до запуска транзакций.

Мое приложение имеет 2 варианта использования. Иногда last_id уже будет использоваться другой строкой, следовательно, он будет проиндексирован ранее; но иногда он генерируется в той же транзакции с помощью предыдущего запроса на вставку, и в этом случае я захожу в тупик.

Мне нужно выполнить обе транзакции до тех пор, пока после оператора удаления. И когда я запускаю insert на tx1, он ожидает блокировки, затем я запускаю insert на tx2, tx2 получает взаимоблокировку и откат.

mysql            | LATEST DETECTED DEADLOCK
mysql            | ------------------------
mysql            | 2019-06-03 21:01:05 0x7f0ba4052700
mysql            | *** (1) TRANSACTION:
mysql            | TRANSACTION 320051, ACTIVE 12 sec inserting
mysql            | mysql tables in use 1, locked 1
mysql            | LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
mysql            | MySQL thread id 286, OS thread handle 139687839577856, query id 17804 172.18.0.1 root update
mysql            | INSERT INTO `properties` (`other_id`) VALUES (@last_id)
mysql            | *** (1) WAITING FOR THIS LOCK TO BE GRANTED:
mysql            | RECORD LOCKS space id 1524 page no 4 n bits 72 index index_properties_on_other_id of table `properties` trx id 320051 lock_mode X insert intention waiting
mysql            | Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
mysql            |  0: len 8; hex 73757072656d756d; asc supremum;;
mysql            | 
mysql            | *** (2) TRANSACTION:
mysql            | TRANSACTION 320052, ACTIVE 8 sec inserting
mysql            | mysql tables in use 1, locked 1
mysql            | 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
mysql            | MySQL thread id 287, OS thread handle 139687973168896, query id 17814 172.18.0.1 root update
mysql            | INSERT INTO `properties` (`other_id`) VALUES (@last_id)
mysql            | *** (2) HOLDS THE LOCK(S):
mysql            | RECORD LOCKS space id 1524 page no 4 n bits 72 index index_properties_on_other_id of table `properties` trx id 320052 lock_mode X
mysql            | Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
mysql            |  0: len 8; hex 73757072656d756d; asc supremum;;
mysql            | 
mysql            | *** (2) WAITING FOR THIS LOCK TO BE GRANTED:
mysql            | RECORD LOCKS space id 1524 page no 4 n bits 72 index index_properties_on_other_id of table `properties` trx id 320052 lock_mode X insert intention waiting
mysql            | Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
mysql            |  0: len 8; hex 73757072656d756d; asc supremum;;
mysql            | 
mysql            | *** WE ROLL BACK TRANSACTION (2)

Состояние блокировок после операторов удаления:

mysql            | ---TRANSACTION 320066, ACTIVE 90 sec
mysql            | 2 lock struct(s), heap size 1136, 1 row lock(s)
mysql            | MySQL thread id 287, OS thread handle 139687973168896, query id 18076 172.18.0.1 root
mysql            | TABLE LOCK table `properties` trx id 320066 lock mode IX
mysql            | RECORD LOCKS space id 1524 page no 4 n bits 72 index index_properties_on_other_id of table `properties` trx id 320066 lock_mode X
mysql            | Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
mysql            |  0: len 8; hex 73757072656d756d; asc supremum;;
mysql            | 
mysql            | ---TRANSACTION 320065, ACTIVE 95 sec
mysql            | 2 lock struct(s), heap size 1136, 1 row lock(s)
mysql            | MySQL thread id 286, OS thread handle 139687839577856, query id 18039 172.18.0.1 root
mysql            | TABLE LOCK table `properties` trx id 320065 lock mode IX
mysql            | RECORD LOCKS space id 1524 page no 4 n bits 72 index index_properties_on_other_id of table ``properties` trx id 320065 lock_mode X
mysql            | Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
mysql            |  0: len 8; hex 73757072656d756d; asc supremum;;

Итак, две транзакции удаляют / вставляют разные other_id с, я не ожидал, что они попадут в тупик. Я хочу узнать, почему именно это происходит.

1 Ответ

1 голос
/ 04 июня 2019

MySQL не блокирует то, чего там нет, например блокирует строки, которые вы не удалили. Также не хранится то, что вы пытались удалить строки с определенным значением «1». Вместо этого он делает отметку в том месте, где должна была быть цифра «1», если бы он там был, и блокирует ее с помощью lock-gap , который имеет следующие характеристики:

Разрывные блокировки в InnoDB являются «чисто тормозящими», что означает, что их единственная цель - предотвратить вставку других транзакций в разрыв . Разрывные замки могут сосуществовать. Блокировка промежутка, принятая одной транзакцией, не мешает другой транзакции захватить блокировку промежутка в том же промежутке . Нет разницы между общими и эксклюзивными блокировками. Они не конфликтуют друг с другом и выполняют одну и ту же функцию.

В пустой таблице место, где был бы 1, находится «где-нибудь в таблице» (или в любом месте от начала до «супремума», упомянутого в тупике), которое, следовательно, блокируется delete. То же самое верно для 2. И эти блокировки не конфликтуют друг с другом по определению.

Но insert делает. Первому insert придется дождаться паузы, которую вызвала вторая транзакция для его удаления. Если вторая транзакция теперь попытается также insert в промежуток, это потребует снятия блокировки с первой транзакции, но это не может произойти, потому что первая транзакция уже ожидает снятия второй блокировки. Итак, вы зашли в тупик.

После того, как вы заполните свой стол, это будет происходить реже, так как пробелы больше не должны охватывать всю таблицу. Если вы, например, уже есть other_id 1 и 3 в вашей таблице, удаление / вставка значений 2 и 4 не будут взаимоблокировать друг друга.

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

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

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

...