Почему MySQL InnoDB устанавливает блокировку S или X Next-Key для повторяющейся записи индекса, когда возникает ошибка дублирования ключа? - PullRequest
1 голос
/ 05 августа 2020

MySQL документ (https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html) упомянут,

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

...

INSERT ... ON DUPLICATE KEY UPDATE отличается от простого INSERT тем, что исключительная блокировка, а не общая блокировка, помещается в строку, которая будет обновлена, когда возникает ошибка дублирования ключа.

и я читал исходный код (https://github.com/mysql/mysql-server/blob/f8cdce86448a211511e8a039c62580ae16cb96f5/storage/innobase/row/row0ins.cc#L1930), соответствующий этой ситуации, InnoDB действительно установил блокировку S или X, когда Возникает ошибка дублирования ключа.

if (flags & BTR_NO_LOCKING_FLAG) {
    /* Set no locks when applying log
    in online table rebuild. */
} else if (allow_duplicates) {
... ...
      
    /* If the SQL-query will update or replace duplicate key we will take
     X-lock for duplicates ( REPLACE, LOAD DATAFILE REPLACE, INSERT ON
     DUPLICATE KEY UPDATE). */
    err = row_ins_set_rec_lock(LOCK_X, lock_type, block, rec, index, offsets, thr);
 } else {
... ...
    err = row_ins_set_rec_lock(LOCK_S, lock_type, block, rec, index, offsets, thr);
}

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

Во-первых, это может легко привести к тупиковой ситуации, в том же документе MySQL было показано 2 примера тупиковой ситуации.

1025 например,
CREATE TABLE `t` (
  `id` int NOT NULL AUTO_INCREMENT,
  `c` int DEFAULT NULL,
  `d` int DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_idx_c` (`c`)
) ENGINE=InnoDB AUTO_INCREMENT=48 DEFAULT CHARSET=utf8mb4

mysql> select * from t;
+----+------+------+
| id | c    | d    |
+----+------+------+
| 30 |   10 |   10 |
| 36 |  100 |  100 |
+----+------+------+

mysql> show variables like '%iso%';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set (0.41 sec)

# Transaction 1
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into t values (null, 100, 100);
ERROR 1062 (23000): Duplicate entry '100' for key 't.uniq_idx_c'

# not commit
# Transcation 2
mysql> insert into t values(null, 95, 95);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into t values(null, 20, 20);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into t values(null, 50, 50);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

# All c in [10, 100] can not be inserted 

1 Ответ

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

Цель базы данных ACID состоит в том, чтобы запросы в вашем сеансе имели тот же результат, если вы попытаетесь запустить их снова.

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

Но что, если другой сеанс обновит строку, вызвавшую конфликт, и изменит уникальное значение? Затем, если вы повторите попытку INSERT, она завершится успешно, что неожиданно.

InnoDB не имеет возможности реализовать истинные транзакции REPEATABLE-READ, когда ваши операторы блокируются. Например, ВСТАВИТЬ / ОБНОВИТЬ / УДАЛИТЬ или даже ВЫБРАТЬ с опциями блокировки ДЛЯ ОБНОВЛЕНИЯ, ДЛЯ ПОДЕЛИТЬСЯ или ЗАБЛОКИРОВАТЬ В РЕЖИМЕ ОБМЕНА. Блокирующие операторы SQL в InnoDB всегда воздействуют на последнюю зафиксированную версию строки, а не на версию этой строки, которая видна вашему сеансу.

Итак, как InnoDB имитирует REPEATABLE-READ, гарантируя, что строка

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

...