MySQL:删除同一行时发生死锁

标签 mysql database transactions innodb database-deadlocks

最近删除记录时遇到死锁(注意隔离级别为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 |
+------------------------------------+------------------------------------------------------------+

show engine innodb status的结果如下所示(最新检测到的死锁部分)

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 状态所示, session B 正在等待下一键锁 C, session A 持有记录锁 C 并等待 C 上的间隙锁;


众所周知

DELETE FROM ... WHERE ... sets an exclusive next-key lock on every record the search encounters

A next-key lock is a combination of a record lock on the index record and a gap lock on the gap before the index record.

Q1:我猜测 session B是否首先获得了间隙锁(next-key的一部分),然后等待记录锁。因此, session A中的后者插入被 session B阻塞(由于间隙锁),最终导致死锁。对吗?

Q2:当C从指数中清除时, session B持有的间隙锁定是否应该是('A','D')?如果是这样,为什么 session A 正在等待范围 (, 'C') 上的插入意图锁?

问题3:为什么 session B有1个行锁,而 session A有4个行锁


Q4:当将索引p_name更改为唯一索引时,我们仍然会由于间隙锁而陷入死锁,这很奇怪。它的行为与官方 doc 不同其中指出仅需要记录锁定。

DELETE FROM ... WHERE ... sets an exclusive next-key lock on every record the search encounters. However, only an index record lock is required for statements that lock rows using a unique index to search for a unique row.


但是使用主键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;                                   |                                      |
+-------------------------------------------+--------------------------------------+

最佳答案

从事务 3631 的“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
  1. 3631 正在等待记录锁。对应的索引内容为{"name":"C", "id": 24}。
  2. 索引名称为表 t 中的 p_name。
  3. 锁的模式是“lock_mode X”

从事务 3630 的“WAITING FOR THIS LOCK TO BE GRANTED”部分,我们可以看到:

*** (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 锁间隙”
  2. 3630 正在持有记录锁。对应的索引内容为{"name":"C","id":24}。持有锁的模式为“lock_mode X locks”
  3. 索引名称为表 t 中的 p_name。
  4. 此死锁是由执行“insert into t (name)values ('B')”引起的

根据您的重现步骤, session A 将发送 delete from t where name = 'C';首先,这将锁定:

  1. ('A', 'C'] 和 ('C', 'D'):下一个键锁定 'C' 并锁定 'D' 之前的间隙锁定;

DELETE FROM ... WHERE ... sets an exclusive next-key lock on every record the search encounters. However, only an index record lock is required for statements that lock rows using a unique index to search for a unique row.

  • 为'C'对应的主索引id添加记录锁。这里的 id 值应该是“26”。
  • 然后 session B 将开始并 delete from t where name = 'C';将再次被执行。然而。对于 session B来说,由于 session A尚未提交,'C'已被 session A锁定。但是,如果执行删除sql, session B将尝试按以下顺序加锁:

    1. 'C'之前的间隙锁:成功,因为innodb可以在同一位置添加多个间隙锁。
    2. 记录锁“C”:已阻止,因为 session A 已持有该锁。 session B必须等待 session A释放它。
    3. “D”之前的间隙锁定:

    最后, session A发送insert into t (name) values ('B'); 。对于表t ,有2个索引,分别是idnameid是一个自增主整型键,对于name,这条sql会尝试添加一个插入意向锁。然而, session B 持有一个间隙锁,因此 session A 必须等待 session B 释放该间隙锁。现在我们可以看看这个死锁是如何发生的。 Innodb会根据成本选择一个 session 进行回滚。这里 session B将被回滚。

    对于第一个问题,答案是肯定的。 实际上,对于第二季度,在 session 提交之前,删除的记录不会从索引中清除。 对于 Q3,行锁编号等于 trx_rows_locked ,而在 mysql 网站中,其:

    TRX_ROWS_LOCKED

    The approximate number or rows locked by this transaction. The value might include delete-marked rows that are physically present but not visible to the transaction.

    从此article ,我们可以知道:

    1. For non-clustered unique index filtering, due to the need to return tables, the number of filtered rows is locked as the unique index plus the number of returned rows.

    2. For non-clustered non-unique index filtering, the gap lock is involved, so more records are locked.

    因此,在 session A 中删除后,trx_rows_locked(间隙锁 + 下一个键锁 + 返回表)为 3。尝试插入后,最终 trx_rows_locked 值应为 3 + 1(插入键锁)。


    以下是新的更新问题: 我之前没有注意到删除主键和唯一辅助键。

    经过一番调查,我发现:

    1. 删除 primary key 时,已被删除且尚未提交,新的删除操作只需要 record lock而不是下一键锁定。
    2. 删除 secondary unique key 时,已被删除且尚未提交,新的删除操作将需要 next-key lock .

    您可以使用set GLOBAL innodb_status_output_locks=ON; show engine innodb status查看正在运行的事务的详细锁定状态。

    关于MySQL:删除同一行时发生死锁,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62846907/

    相关文章:

    mysql - 如何根据最常见的字段更新值?

    mysql OR/AND - 操作顺序如何工作

    mysql - 如何编写 SQL 来统计移动/平板/桌面用户的数量?

    Android FragmentManager beginTransaction.add 添加 fragment 到隐藏容器

    php - 使用php将xml转换为mysql?

    php - laravel 获取多个结果集

    php - 特定国家/地区的 Magento 产品

    php - 为什么我不能在 Wordpress 中创建新帖子?第 716 行的警告 : Creating default object from empty value in/public_html/wp-admin/includes/post. php

    spring - 如何将(@Autowired)@Service注入(inject)ConstraintValidator?

    java - 如何配置事务管理以在 Spring 中使用 2 个不同的数据库?