mysql 死锁(先delete 后insert)日志分析

本文深入探讨了MySQL中死锁的发生原因,通过具体案例分析了死锁的产生过程,并提出了有效的解决策略,包括使用主键更新替代删除插入操作及调整事务隔离级别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:

通过show engine innodb status,获取最近一次发生的死锁详细信息。

推荐分析详细信息的文章

https://www.cnblogs.com/olinux/p/5497176.html

背景:

项目最近代码发生了一次死锁,大致场景是A表数据和B表数据需要保存在中间表C中,但是A的Id在C表中只能有一条;

当时想的方案是在每次insert前先根据A的id做一次删除,再将最新的数据保存进C表;

由于对mysql锁的机制不是很了解,导致了一些问题。

 

下面我们重现一下当时的场景并作死锁日志的分析

建表语句

CREATE TABLE `table_c` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `aid` bigint(20) NOT NULL,
  `bids` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_aid` (`aid`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=latin1

插入两条数据 

INSERT INTO table_c (aid, bids) VALUES (1, '1,2,4');
INSERT INTO table_c (aid, bids) VALUES (2, '44,55');

事务隔离级别

show variables  like '%isolation%';

 

然后按照以下流程执行sql(高并发的场景下出现)

-----client1

begin
delete from table_c where aid = 3;
insert table_c (aid, bids) values(3,'44,55');
commit;

-----client2

begin
delete from table_c where aid = 6;
insert table_c (aid, bids) values(6,'448,558');
commit;

你会发现当客户端2执行到inert语句的时候就会死锁的异常了:

日志分析:

通过 show engine innodb status ; 获得最近一次mysql的死锁日志

------------------------
LATEST DETECTED DEADLOCK
------------------------
2019-09-21 18:50:02 1930
*** (1) TRANSACTION:
TRANSACTION 7427, ACTIVE 8 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 360, 2 row lock(s), undo log entries 1
MySQL thread id 29, OS thread handle 0x3994, query id 2481 localhost 127.0.0.1 root update
/* ApplicationName=DataGrip 2019.1.4 */ insert table_c (aid, bids) values(3,'44,55')
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 40 page no 4 n bits 72 index `idx_aid` of table `test`.`table_c` trx id 7427 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

*** (2) TRANSACTION:
TRANSACTION 7428, ACTIVE 6 sec inserting, thread declared inside InnoDB 5000
mysql tables in use 1, locked 1
3 lock struct(s), heap size 360, 2 row lock(s), undo log entries 1
MySQL thread id 30, OS thread handle 0x1930, query id 2491 localhost 127.0.0.1 root update
/* ApplicationName=DataGrip 2019.1.4 */ insert table_c (aid, bids) values(6,'448,558')
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 40 page no 4 n bits 72 index `idx_aid` of table `test`.`table_c` trx id 7428 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 40 page no 4 n bits 72 index `idx_aid` of table `test`.`table_c` trx id 7428 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

*** WE ROLL BACK TRANSACTION (2)

通过日志分析 TRANSACTION(1)和TRANSACTION(2)都在等待排他的插入意向锁(关键字:WAITING FOR THIS LOCK TO BE GRANTED:),在这个等待之前TRANSACTION(2)持有了一个排他记录锁(关键字:HOLDS THE LOCK(S),这个锁是delete语句获得的,从日志分析和表数据分析可知delete语句获得了aid在 6-73757072656d756d(16进制)之间数据的锁);

虽然日志里没有打印TRANSACTION(1)在等待插入意向锁前获得了什么锁,但是通过推断,我们可以分析出TRANSACTION(1)也应该是持有了一个排他记录锁(delete语句);

这样双方都在等待对方释放锁,就导致了死锁

解决方法:

可以通过先查询,再根据ID主键修改的方式

修改事务隔离级别

mysql死锁经典案例:

当前读S锁与排他X锁之间的竞争

https://blog.youkuaiyun.com/u013592964/article/details/88942189

非主键索引锁和主键索引锁之间的竞争

https://blog.youkuaiyun.com/lzy_lizhiyang/article/details/52678446

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值