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