MySQL: взаимоблокировка произошла при удалении одной и той же строки дважды в (уникальном) вторичном индексе - PullRequest
2 голосов
/ 11 июля 2020

Недавно я столкнулся с тупиком при удалении записей (обратите внимание, что уровень изоляции: REPEATABLE READ , MySQL 5.7)

Вот шаги воспроизведения

1 Создать новую таблицу

CREATE TABLE `t` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `p_name` (`name`)
) ENGINE=InnoDB CHARSET=utf8;

2 Подготовить 3 записи

insert into t (name) value ('A'), ('C'), ('D');

3

+====================================+============================================================+
|             Session A              |                         Session B                          |
+====================================+============================================================+
| begin;                             |                                                            |
+------------------------------------+------------------------------------------------------------+
|                                    | begin;                                                     |
+------------------------------------+------------------------------------------------------------+
| delete from t where name = 'C';    |                                                            |
+------------------------------------+------------------------------------------------------------+
|                                    | delete from t where name = 'C';  --Blocked!                |
+------------------------------------+------------------------------------------------------------+
| insert into t (name) values ('B'); |                                                            |
+------------------------------------+------------------------------------------------------------+
|                                    | ERROR 1213 (40001): Deadlock found when trying to get lock |
+------------------------------------+------------------------------------------------------------+

Результат показать статус innodb движка , как показано ниже (секция ПОСЛЕДНИЕ ОБНАРУЖЕННАЯ БЛОКИРОВКА)

LATEST DETECTED DEADLOCK
------------------------
*** (1) TRANSACTION:
TRANSACTION 3631, ACTIVE 21 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 13, OS thread handle 123145439432704, query id 306 localhost root updating
delete from t where name = 'C'
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 69 page no 4 n bits 72 index p_name of table `jacky`.`t` trx id 3631 lock_mode X waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 1; hex 43; asc C;;
 1: len 8; hex 8000000000000018; asc         ;;

*** (2) TRANSACTION:
TRANSACTION 3630, ACTIVE 29 sec inserting
mysql tables in use 1, locked 1
5 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2
MySQL thread id 14, OS thread handle 123145439711232, query id 307 localhost root update
insert into t (name) values ('B')
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 69 page no 4 n bits 72 index p_name of table `jacky`.`t` trx id 3630 lock_mode X
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 1; hex 43; asc C;;
 1: len 8; hex 8000000000000018; asc         ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 69 page no 4 n bits 72 index p_name of table `jacky`.`t` trx id 3630 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 1; hex 43; asc C;;
 1: len 8; hex 8000000000000018; asc         ;;

Как показано в статусе Innodb, сеанс B ожидает блокировки следующего ключа C, а сеанс A удерживает блокировку записи C и ожидает блокировку пропусков C;

Как мы все знаем,

DELETE FROM ... WHERE ... устанавливает исключительную блокировку следующего ключа для каждой записи, с которой встречается поиск

Блокировка следующей клавиши - это комбинация блокировки записи в индексной записи и блокировки промежутка в промежутке перед индексной записью.

Q1 : Я думаю, если сеанс B сначала получил блокировку промежутка (часть следующего ключа), а затем ожидал блокировки записи. Таким образом, последняя вставка в сеансе A была заблокирована сеансом B (из-за блокировки промежутка) и в конечном итоге привела к мертвой блокировке. Правильно?

Q2 : Поскольку C удаляется из индекса, удерживается ли блокировка пропусков сеансом B ('A', 'D ')? Если да, то почему сеанс A ожидает блокировки интенсификации вставки в диапазоне (, 'C')?

Q3 : почему сеанс B имеет 1 row lock(s), а сеанс A имеет 4 row lock(s)?

Q4 : при изменении индекса p_name на уникальный индекс мы все равно получаем тупик из-за блокировки пробела, это странно. Его поведение отличается от официального do c, в котором говорится, что требуется только блокировка записи.

DELETE FROM ... WHERE ... устанавливает исключительную блокировку следующего ключа для каждого записывать поисковые встречи. Однако требуется только блокировка записи индекса для операторов, которые блокируют строки с использованием уникального индекса для поиска уникальной строки .

Однако это нормально при использовании первичного ключа id для выполнения удаления (шаги, указанные ниже). Это ошибка в MySQL?

1 Подготовьте данные

delete from t;
insert into t (id, name) value (1, 'A'), (3, 'C'), (5, 'D');

2

+-------------------------------------------+--------------------------------------+
|                 Session A                 |              Session B               |
+-------------------------------------------+--------------------------------------+
| begin;                                    |                                      |
|                                           | begin;                               |
| delete from t where id = 3;               |                                      |
|                                           | delete from t where id = 3; Blocked! |
| insert into t (id, name) values (2, 'B'); |                                      |
|                                           |                                      |
| commit;                                   |                                      |
+-------------------------------------------+--------------------------------------+

Ответы [ 2 ]

1 голос
/ 03 августа 2020

Для Q4 , я наконец нашел комментарий в исходном коде MySQL 5.7 , объясняющий, почему используются блокировки следующего ключа, только для справки.

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

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

Обратите внимание, что вторичный индекс UNIQUE может содержать множество строк с одним и тем же значением ключа, если один из столбцов имеет значение SQL null. Кластеризованный индекс под MySQL никогда не может содержать пустые столбцы, потому что мы требуем, чтобы все столбцы в первичном ключе были ненулевыми.

1 голос
/ 15 июля 2020

Из части транзакции 3631 «ОЖИДАНИЕ БЛОКИРОВКИ ЭТОЙ БЛОКИРОВКИ» мы могли увидеть:

RECORD LOCKS space id 69 page no 4 n bits 72 index p_name of table `jacky`.`t` trx id 3631 lock_mode X waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
  1. 3631 ожидает блокировки записи. Соответствующее содержимое индекса: {"name": "C", "id": 24}.
  2. Имя индекса - p_name в таблице t.
  3. режим блокировки - "lock_mode X «

Из части транзакции 3630« ОЖИДАНИЕ БЛОКИРОВКИ »мы могли увидеть:

*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 69 page no 4 n bits 72 index p_name of table `jacky`.`t` trx id 3630 lock_mode X
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 1; hex 43; asc C;;
 1: len 8; hex 8000000000000018; asc         ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 69 page no 4 n bits 72 index p_name of table `jacky`.`t` trx id 3630 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 32
 0: len 1; hex 43; asc C;;
 1: len 8; hex 8000000000000018; asc         ;;
  1. 3630 ожидает блокировки записи. Соответствующее содержимое индекса: {"name": "C", "id": 24}. режим ожидания блокировки - «lock_mode X locks gap»
  2. 3630 удерживает блокировку записи. Соответствующее содержимое индекса: {"name": "C", "id": 24}. Режим удерживающей блокировки - «lock_mode X locks»
  3. Имя индекса - p_name в таблице t.
  4. Этот тупик вызван выполнением команды «вставить в значения t (имя) ('B')»

В соответствии с вашим шагом воспроизведения сеанс A сначала отправит delete from t where name = 'C';, это заблокирует:

  1. ('A', 'C'] и ('C', 'D'): блокировка следующей клавиши 'C' и блокировка пробела перед 'D';

УДАЛИТЬ ОТ ... ГДЕ ... устанавливает исключительную блокировку следующего ключа для каждой записи, обнаруживаемой при поиске. Однако для операторов, которые блокируют строки с использованием уникального индекса для поиска уникальной строки, требуется только блокировка записи индекса.

добавить блокировку записи для соответствующего идентификатора первичного индекса 'C'. здесь значение id должно быть «26».

После этого начнется сеанс B и снова будет выполнено delete from t where name = 'C';. Тем не мение. Для сеанса B, поскольку сеанс A не был зафиксирован, 'C' был заблокирован сеансом A. Однако, если выполнить удаление sql, сеанс B попытается добавить блокировку в следующей последовательности:

  1. блокировка зазора перед 'C': Успех, поскольку innodb может добавить блокировку с несколькими зазорами в той же позиции.
  2. блокировка записи 'C': Заблокировано, потому что сеанс A удерживал эту блокировку. сеанс B должен дождаться его освобождения сеансом A.
  3. блокировка пробела перед 'D':

Наконец, сеанс A отправляет insert into t (name) values ('B');. Для таблицы t есть 2 индекса: id и name. id - первичный целочисленный ключ с автоматическим увеличением, и для name этот sql попытается добавить блокировку намерения вставки. Однако существует блокировка промежутка, удерживаемая сеансом B, поэтому сеанс A должен ждать сеанс B для снятия блокировки этого промежутка. Теперь мы могли видеть, как происходит эта тупиковая блокировка. Innodb выберет сеанс для отката по стоимости. Здесь будет выполнен откат сеанса B.

Для Q1 ответ - да. На самом деле для Q2 удаленная запись не будет удалена из индекса до фиксации сеанса. Для Q3 номер блокировки строки равен trx_rows_locked, а на веб-сайте mysql это:

TRX_ROWS_LOCKED

Примерное число или строки заблокированы этой транзакцией. Значение может включать в себя отмеченные для удаления строки, которые физически присутствуют, но не видны транзакции.

Из этой статьи мы могли бы знать:

Для фильтрации некластеризованного уникального индекса из-за необходимости возвращать таблицы количество отфильтрованных строк блокируется как уникальный индекс плюс количество возвращенных строк.

Для некластеризованной фильтрации неуникального индекса задействована блокировка промежутка, поэтому блокируется больше записей.

Итак, trx_rows_locked (блокировка промежутка + блокировка следующей клавиши + таблица возврата) равно 3 после удаления в сеансе A. окончательное значение trx_rows_locked должно быть 3 + 1 (блокировка вставки ключа) после попытки вставки.

Следующие вопросы относятся к новым вопросам обновления: я раньше не замечал удаления первичного ключа и уникального вторичного ключа.

После некоторого расследования я обнаружил:

  1. При удалении a primary key, которые были удалены и еще не зафиксированы, новая операция удаления потребует только record lock вместо блокировки следующей клавиши.
  2. При удалении secondary unique key, который был удален и еще не зафиксирован, для новой операции удаления потребуется next-key lock.

Вы можете использовать set GLOBAL innodb_status_output_locks=ON; show engine innodb status, чтобы увидеть состояние блокировки деталей для выполнения транзакций.

...