原标题:MySQL中RR模式下死锁一例

作者:高鹏(八怪),《深入理解MySQL主从原理32讲》系列的作者

原文出处:https://www.jianshu.com/p/3e57a428d2a2

一、案例模拟二、死锁分析三、关于锁模式的变化四、关于LOCK_ORDINARY[next_key_lock]来历最后

环境:版本5.7.29,RR隔离级别

一、案例模拟CREATETABLE`t8`(

`id`bigint( 20) NOTNULLAUTO_INCREMENT,

`d_id`varchar( 40) NOTNULLDEFAULT'',

`b_id`varchar( 40) NOTNULLDEFAULT'',

`is_dropped`tinyint( 1) NOTNULLDEFAULT'0',

`u_c`varchar( 10) NOTNULLDEFAULT'',

PRIMARY KEY( `id`),

UNIQUEKEY`DealerAndBrokerAndDropped`( `d_id`, `b_id`, `is_dropped`)

) ENGINE= InnoDB;

insertintot8 values( 1, 1, 1, 0, 'a');

insertintot8 values( 2, 2, 2, 0, 'a');

insertintot8 values( 3, 3, 3, 0, 'a');

insertintot8 values( 4, 4, 4, 0, 'a');

insertintot8 values( 5, 5, 5, 0, 'a');

insertintot8 values( 6, 6, 6, 0, 'a');

insertintot8 values( 7, 7, 7, 0, 'a');

insertintot8 values( 8, 8, 8, 0, 'a');

insertintot8 values( 9, 9, 9, 0, 'a');

insertintot8 values( 10, 10, 10, 0, 'a');

insertintot8 values( 11, 11, 11, 0, 'a');

执行语句如下:

S1

S2

begin

select u_c from t8 where d_id='1' and b_id='1' and is_dropped=0 for update;

select u_c from t8 where d_id='1' and b_id='1' and is_dropped=0 for update; 处于堵塞状态

update t8 set u_c='b' where d_id='1' and b_id='1'; —此时触发死锁 S2回滚

发生死锁记录如下:

二、死锁分析

仔细分析我们会发现trx id 5679最后被堵塞需要获取的锁为(lock_mode X waiting),堵塞发生在索引DealerAndBrokerAndDropped 上,也就是这是一个next key lock 且需要获取的模式为LOCK_X,处于等待状态。

而我们来看trx id 5679前面获取的锁是什么呢?显然可以看到为(lock_mode X locks rec but not gap),获取发生在索引DealerAndBrokerAndDropped 上,也就是这是一个key lock且获取模式为LOCK_X。

但是我们需要知道DealerAndBrokerAndDropped明明是一个唯一索引,获取key lock我们很容易理解,但是为什么也会出现获取next key lock呢?这个问题我们先放一下,先来分析一下整个死锁的产生的过程

S1(select操作)

通过唯一性索引定位索引数据获取了唯一索引DealerAndBrokerAndDropped 上的

LOCK_REC_NOT_GAP|LOCK_X,获取成功记录就是 d_id='1' b_id='1' is_dropped=0这条数据。

S1(select操作)

回表获取全部数据,这个时候需要主键上的相应的行锁。LOCK_REC_NOT_GAP|LOCK_X获取成功

S2(select操作)

通过唯一性索引定位索引数据试图获取了唯一索引DealerAndBrokerAndDropped 上的

LOCK_REC_NOT_GAP|LOCK_X,获取失败记录就是 d_id='1' b_id='1' is_dropped=0这条数据,处于等待状态。

S1(update操作)

通过索引DealerAndBrokerAndDropped 查找数据(注意这里已经不是唯一性定位操作了,下面会做分析),这个时候首先需要通过查询条件获取出需要更新的第一条数据,实际上这个时候也是d_id='1' b_id='1' is_dropped=0这条数据,需要获取的锁为LOCK_ORDINARY[next_key_lock]|LOCK_X,这个时候我发现虽然S1之前获取了这条数据的锁,但是锁模式变化了(一致不会重新获取,下面会分析这种行为),因此这里需要重新获取,但是这显然是不行的,因为S2都还处于等待中,因此这里也发生了等待。

因此通过这个过程就出现死锁,S2等S1 S1等S2。

三、关于锁模式的变化

关于这里我们参考函数lock_rec_lock_fast,这里会不进行行锁冲突验证而进行快速加锁,如果锁模式没有变化则也会再这里进行快速加锁(也就是直接跳过),当然如果块中一个row lock 都没有也会在这里进行加锁,这是每个加行锁的操作都必须经历的判断,如果不能快速加锁则进入slow加锁方式,这里看一下下面的这段代码:

if(lock_rec_get_next_on_page( lock)

|| lock->trx != trx

|| lock->type_mode != (mode | LOCK_REC)

|| lock_rec_get_n_bits( lock) <= heap_no) {

status = LOCK_REC_FAIL;

}

这里的lock->trx != trx会判断本次加锁事务和上次加锁事务是否是同一个事务,lock->type_mode != (mode | LOCK_REC)会判断锁模式是否相同。如果不能满足条件则判定为LOCK_REC_FAIL,进入slow加锁方式。

而我们这里S1加锁第一次是LOCK_REC_NOT_GAP|LOCK_X,而第二次是LOCK_ORDINARY[next_key_lock]|LOCK_X,显然变化了,因此进入slow加锁阶段,进行冲突验证,结果嘛也就冲突了。这是本死锁的一个原因。

四、关于LOCK_ORDINARY[next_key_lock]来历

这是本死锁的一个最重要原因,知道了这个原因这个案例就理解了。首先我们先看这个update语句:

updatet8 setu_c= 'b'whered_id= '1'andb_id= '1';

我们发现这个时候唯一索引还少一个条件也就是is_dropped字段,这个时候本次定位查询不会判定为唯一性查询,而是普通的二级索引定位方式,这个时候RR模式出现LOCK_ORDINARY[next_key_lock]就显得很自然了,下面是这个判断过程,代码位于row_search_mvcc中。

(match_mode == ROW_SEL_EXACT

&& dict_index_is_unique( index)

&& dtuple_get_n_fields(search_tuple)

== dict_index_get_n_unique( index)

&& (dict_index_is_clust( index)

|| !dtuple_contains_null(search_tuple)))

稍微解释一下,唯一性查找条件至少包含如下3点:

1. 索引具有唯一性

2. 查询的字段数量和索引唯一性字段数量相同

3. 是主键或者查询条件中不包含NULL值

注意第3点源码说明如下:

/* Note above that a UNIQUE secondary index can contain many

rows with the same key value if one of the columns is the SQL

null. A clustered index under MySQL can never contain null

columns because we demand that all the columns in primary key

are non-null. */

满足上面4点条件才能确认为唯一查找,本查询由于第3条不满足因此,因此判定失败。

不仅如此如果本条数据加锁成功,那么你会看到如下的结果:

---TRANSACTION 25830, ACTIVE 2 sec

4 lockstruct(s), heapsize1160, 3rowlock(s), undologentries 1

MySQL threadid5, OS threadhandle 140737101231872, queryid4115localhost root starting

showengineinnodbstatus

TABLELOCKtable`test`. `t8`trx id25830lockmodeIX

RECORDLOCKS spaceid1050page no4n bits 80indexDealerAndBrokerAndDropped oftable`test`. `t8`trx id25830lock_mode X

Recordlock, heapno2PHYSICALRECORD: n_fields 4; compact format; info bits 0

0: len 1; hex 31; asc 1;;

1: len 1; hex 31; asc 1;;

2: len 1; hex 80; asc ;;

3: len 8; hex 8000000000000001; asc ;;

RECORD LOCKS space id 1050 page no 3 n bits 80 index PRIMARY of table `test`.`t8` trx id 25830 lock_mode X locks rec but not gap

Record lock, heapno2PHYSICALRECORD: n_fields 7; compact format; info bits 0

0: len 8; hex 8000000000000001; asc ;;

1: len 6; hex 0000000064e6; asc d ;;

2: len 7; hex 5f000000430110; asc _ C ;;

3: len 1; hex 31; asc 1;;

4: len 1; hex 31; asc 1;;

5: len 1; hex 80; asc ;;

6: len 1; hex 62; asc b;;

RECORD LOCKS space id 1050 page no 4 n bits 80 index DealerAndBrokerAndDropped of table `test`.`t8` trx id 25830 lock_mode X locks gap before rec

Record lock, heapno11PHYSICALRECORD: n_fields 4; compact format; info bits 0

0: len 2; hex 3130; asc 10;;

1: len 2; hex 3130; asc 10;;

2: len 1; hex 80; asc ;;

3: len 8; hex 800000000000000a; asc ;;

我们发现DealerAndBrokerAndDropped唯一索引的吓一跳记录也加了gap lock,这完全是RR模式非唯一索引的加锁行为。

最后

如果我们将语句

updatet8 setu_c= 'b'whered_id= '1'andb_id= '1';

修改为

updatet8 setu_c= 'b'whered_id= '1'andb_id= '1'andis_dropped= 0;

那么死锁将不会触发了。原因就是第三部分我们说的,这里锁模式完全一致,不会导致加锁操作了。

enjoy MySQL :)返回搜狐,查看更多

责任编辑:

mysql lock trx id_MySQL中RR模式下死锁一例相关推荐

  1. mysql len hex asc_MySQL中RR模式下死锁一例

    原标题:MySQL中RR模式下死锁一例 作者:高鹏(八怪),<深入理解MySQL主从原理32讲>系列的作者 原文出处:https://www.jianshu.com/p/3e57a428d ...

  2. vmware中NAT模式下,虚拟机与主机能ping通 为什么虚拟机不能上网

    vmware中NAT模式下,虚拟机与主机能ping通 为什么虚拟机不能上网? 方案一: 1.把虚拟机的网络连接设置为桥接或NAT都可以的 2.把虚拟机和主机设置为同一网段 主机 网络邻居属性 3.双击 ...

  3. 帧中继环境中NBMA模式下OSPF的配置

    帧中继环境中NBMA模式下OSPF的配置 在帧中继服务器添加端口 R1(config)#int s1/0 R1(config-if)#ip add 172.16.134.1 255.255.255.0 ...

  4. mysql rr和rc_MySQL的在RC和RR模式下的锁

    InnoDB的锁机制: 数据库使用所是为了支持更好的并发,提供数据的完整性和一致性.InnoDB是一个支持锁的存储引擎,锁的类型有:共享锁(S).排它锁(X).意向共享锁(IS).意向排它锁(IX). ...

  5. rc 和rr 模式下的死锁问题

    问题描述:线上项目高峰期突然出现死锁问题!用户操作出现卡顿,后台直接抛出deadlock 查看日志发现是在update表的时候出现DeadlockLoserDataAccessException异常 ...

  6. mysql 必须安装php_非root模式下安装mysql php小记

    假设你的home目录为/home/work mysql-server 安装 1. 下载mysql.tar.gz wget http://dev.mysql.com/get/Downloads/MySQ ...

  7. vs2010中release模式下调试程序

    debug模式调试信息全,但是速度很慢,在数据量比较大的时候非常影响调试效率,release模式速度快,但是没有调试信息.所以在编译的时候很多编译器会提供一种折中的编译方式,在release下提供调试 ...

  8. 康耐视InSight中电子表格模式下图案匹配工具FindPatterns的使用分享

    1. 准备工作: 打开InSight软件,选择电子表格视图. 修改仿真器:以下所有的程序演示都是在仿真上进行的,请预先将仿真器型号改为标准(见:https://blog.csdn.net/qq_346 ...

  9. png图片在unity中Default模式下透明区域显示白底的问题

    最近在项目开发中遇到一个问题,美术给的png图片在unity中查看的时候Default选项下透明区域会显示黑白色,用于spine动画中会显示白色的色块.但是透明区域显示黑色的地方正常. 打印图片每个像 ...

最新文章

  1. 伪基站识别技巧(一)
  2. 我的年龄又快被5整除了......
  3. “苹果光环”褪色后,瑞声靠什么坐稳头把交椅?
  4. 怎么判断ajax返回是否成功,如何判断jquery的ajax请求已经返回
  5. flask的第一个hello word 程序
  6. leetcode1328. 破坏回文串
  7. 前端学习(1905)vue之电商管理系统电商系统之根据用户id查询对应的信息
  8. 微信小程序实现轨迹回放
  9. 基于FFmpeg的音频编码(PCM数据编码成AAC android)
  10. v540 检测的问题
  11. hbase windows 单机版安装
  12. spring使用 hibernate jpa JpaRepository
  13. 基于51单片机的模拟信号检测系统
  14. 基于SSM房屋租赁管理系统
  15. 丁火生于未月命理分析_丁火生于未月的性格特征
  16. STM8L051的硬件I2C调试
  17. 《设计模式》之命令模式
  18. 2345浏览器怎么换主页 2345浏览器换主页教程
  19. 考研数学之线性代数知识点
  20. 雅礼集训 Day1 T1 养花

热门文章

  1. 基于JAVA+SpringMVC+Mybatis+MYSQL的集市预约管理系统
  2. 基于JAVA+SpringMVC+Mybatis+MYSQL的教材管理系统
  3. 【转载】手动删除引用nuget如何还原
  4. 章节三、2-方法_演示实例
  5. java怎样获取变量的类型
  6. NodeJS-001-Nodejs学习文档整理(转-出自http://www.cnblogs.com/xucheng)
  7. Linux 下 Shell 命令的分类及用法
  8. linux添加用户、权限
  9. USACO3.1.1最短网络
  10. dijkstra java pre_Dijkstra算法实现