MySQL版本:5.6.36-82.1-log Mybatis-Plus的慎用starter版本:3.3.2存储引擎:InnoDB
A同学在生产环境使用了Mybatis-Plus提供的 com.baomidou.mybatisplus.extension.service.IService#saveOrUpdate(T, com.baomidou.mybatisplus.core.conditions.Wrapper) 方法(以下简称B方法),并发场景下,慎用数据库报了如下错误
如上图示,数据库报了死锁,慎用那死锁场景千万种,慎用为什么确定B方法是慎用由于间隙锁导致的死锁?
两个事务互相等待对方持有的锁,导致互相阻塞,慎用从而导致死锁。
MySQL会向左找第一个比当前索引值小的值,向右找第一个比当前索引值大 的值(没有则为正无穷),将此区间锁住,从而阻止其他事务在此区间插入数据。
与Record lock组合成Next-key lock,在可重复读这种隔离级别下一起工作避免幻读。
理论上一款开源的框架,经过了多年打磨,提供的方法不应该造成如此严重的错误,但理论仅仅是理论上,事实就是发生了死锁,于是我们开始了一轮深度排查。首先我们从这个方法的源码入手,源码如下:
default boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper) { return this.update(entity, updateWrapper) || this.saveOrUpdate(entity); }
从源码上看此方法就没有按套路出牌,正常逻辑应该是首先执行查询,存在则修改,不存在则新增,但此方法上来就执行了修改。我们就猜想是不是MySQL在修改时增加了什么锁导致了死锁,于是我们找到了DBA获取了最新的死锁日志,即执行show engine innodb status,我们发现了两项关键信息如下:
*** (1) TRANSACTION:...省略日志*** (1) WAITING FOR THIS LOCK TO BE GRANTED:RECORD LOCKS space id 0 page no 347 n bits 80 index `PRIMARY` of table `database_name`.`table_name` trx id 71C lock_mode X locks gap before rec insert intention waiting *** (2) TRANSACTION:...省略日志*** (2) WAITING FOR THIS LOCK TO BE GRANTED:RECORD LOCKS space id 0 page no 347 n bits 80 index `PRIMARY` of table `database_name`.`table_name` trx id 71D lock_mode X locks gap before rec insert intention waiting
简单翻译一下,就是事务一在获取插入意向锁时,需要等待间隙锁(事务二添加)释放,同时事务二在获取插入意向锁时,也在等待间隙锁释放(事务一添加),(本文不讨论MySQL在修改与插入时添加的锁,我们把修改时添加间隙锁,插入时获取插入意向锁为已知条件)那我们回到B方法,并发场景下,是不是就很大几率会满足事务一和事务二相互等待对方持有的间隙锁,从而导致死锁。
现在我们理论有了,我们现在用真实数据来验证此场景。
create table t_gap_lock(id int auto_increment primary key comment '主键ID',name varchar(64) not null comment '名称',age int not null comment '年龄') comment '间隙锁测试表';
mysql> select * from t_gap_lock;+----+------+-----+| id | name | age |+----+------+-----+| 1 | 张三 | 18 || 5 | 李四 | 19 || 6 | 王五 | 20 || 9 | 赵六 | 21 || 12 | 孙七 | 22 |+----+------+-----+
mysql> begin;Query OK, 0 rows affected (0.00 sec)mysql> update t_gap_lock t set t.age = 25 where t.id = 4;Query OK, 0 rows affected (0.00 sec)Rows matched: 0 Changed: 0 Warnings: 0
mysql> begin;Query OK, 0 rows affected (0.00 sec)mysql> update t_gap_lock t set t.age = 25 where t.id = 7;Query OK, 0 rows affected (0.00 sec)Rows matched: 0 Changed: 0 Warnings: 0
mysql> insert into t_gap_lock(id, name, age) value (7,'间隙锁7',27);
mysql> SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS \G;(责任编辑:热点)
TCL科技(000100.SZ)公布消息:近日增持中环股份股票共5855.2778万股
63家百亿元级私募成绩单:4家的年度收益率保持在80%至100%之间
10月8日全国加油站调整后92、95汽油新售价 价格降了吗?
寰亚传媒(08075.HK)中期亏损收窄至1916万港元 每股亏损4.58港仙