mysql - 为什么并发 "Delete...Insert"语句会导致死锁?

标签 mysql transactions

考虑 mysql 中的以下模式:

create table foo(
  id int not null primary key auto_increment,
  name varchar(32) not null,
  unique key(name)
);

表中有一条名为“abc”的记录。

我有一笔交易(RC):

start transaction;
delete from foo where name = "abc";
insert into foo(name) values("abc");
commit;

如果有两个并发事务,就会发生死锁。

       |        TX A         |             TX B
---------------------------------------------------------------------
Step 1 | start transaction;  | 
       | delete name="abc";  |
---------------------------------------------------------------------
Step 2 |                     | start transaction;
       |                     | delete name="abc";
       |                     | <wait for lock>
---------------------------------------------------------------------
Step 3 | insert name="abc";  | <deadlock detected, exit>
---------------------------------------------------------------------
Step 4 | commit;             |
---------------------------------------------------------------------

我想知道为什么这个序列会导致死锁。

在 mysql 文档中说( https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html )

If a duplicate-key error occurs, a shared lock on the duplicate index record is set. This use of a shared lock can result in deadlock should there be multiple sessions trying to insert the same row if another session already has an exclusive lock. This can occur if another session deletes the row.

我想当事务A运行“delete”语句时,它已经获取了记录“abc”的X锁。当“insert”语句执行时,由于“重复键错误”,它尝试获取S锁。既然它已经获得了同一条记录的X锁,那么它不应该获得S锁吗?为什么这里会发生死锁?

最佳答案

我重现了死锁,得到的innoDB状态日志如下:

------------------------
LATEST DETECTED DEADLOCK
------------------------
2019-10-18 18:35:14 0x7f1dfc738700
*** (1) TRANSACTION:
TRANSACTION 26547965, ACTIVE 6 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
/* ApplicationName=DataGrip 2019.1.1 */ delete from foo where name='abc'
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 3011 page no 4 n bits 224 index IDX_NAME of table `foo` trx id 26547965 lock_mode X locks rec but not gap waiting
Record lock, heap no 153 PHYSICAL RECORD: n_fields 2; ....

*** (2) TRANSACTION:
TRANSACTION 26547960, ACTIVE 10 sec inserting
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1136, 3 row lock(s), undo log entries 2
/* ApplicationName=DataGrip 2019.1.1 */ INSERT INTO foo(id, name)
VALUES (1, 'abc')
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 3011 page no 4 n bits 224 index IDX_NAME of table `foo` trx id 26547960 lock_mode X locks rec but not gap
Record lock, heap no 153 PHYSICAL RECORD: ...

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 3011 page no 4 n bits 224 index IDX_NAME of table `foo` trx id 26547960 lock mode S waiting
Record lock, heap no 153 PHYSICAL RECORD: ....

*** WE ROLL BACK TRANSACTION (1)

日志清楚地解释了原因,TX B 等待 TX A 持有的 X 锁,同时 TX A 等待 S 锁,该锁被 TX B 的锁请求阻塞。

根据Mysql文档:

If a duplicate-key error occurs, a shared lock on the duplicate index record is set. This use of a shared lock can result in deadlock should there be multiple sessions trying to insert the same row if another session already has an exclusive lock. "

Insert语句确实在某个时刻获取了S锁,所以死锁发生的原因很清楚。

但问题是:

  • 根据 mysql 文档,如果发生重复键错误,insert 语句将获取 S 锁,但在我们讨论的当前情况下没有发生这种情况
  • 当当前事务已经持有X锁时,为什么插入语句仍然获取S锁,X锁足以进行当前读取以检查重复键错误。那么它有什么用呢?

关于mysql - 为什么并发 "Delete...Insert"语句会导致死锁?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58499387/

相关文章:

java - mysql:查找具有作为 "string"的子字符串的字段的行

MySql 按日期对 2 个或多个表中的列进行排序

php - 让3人从sql中获得更高的值(value)

java - 注释类型 Transactional 的属性 readOnly 未定义

java - JMS 监听器发生异常时会发生什么

transactions - EJB (3.1) 容器内 iBATIS 3 中的事务管理

php - MySQL 中的日期时间显示

SQL Server 到 MYSQL 迁移工具

.net - 为什么我应该使用 MSDTC 上的事务而不是 System.Data.SqlClient.SqlTransaction?

java - 如何在 Infinispan 中使用 JTA 事务?