mysql for update场景_一个mysql死锁场景实例分析

前言

最近遇到一个mysql在RR级别下的死锁问题,感觉有点意思,研究了一下,做个记录。

涉及知识点:共享锁、排他锁、意向锁、间隙锁、插入意向锁、锁等待队列

场景

隔离级别:Repeatable-Read

表结构如下

create table t (

id int not null primary key AUTO_INCREMENT,

a int not null default 0,

b varchar(10) not null default '',

c varchar(10) not null default '',

unique key uniq_a_b(a,b),

unique key uniq_c(c)

);

初始化数据

insert into t(a,b,c) values(1,'1','1');

有A/B两个session,按如下顺序执行两个事务

de8b4e997d370049f217cf1adef3cc27.png

结果是

B执行完4之后还是一切正常

A执行5的时候,被block

B接着执行6,B报死锁,B回滚,A插入数据

show engine innodb status中可以看到死锁信息,这里先不贴,先解释几种锁的概念,再来理解死锁过程

共享(S)锁/互斥(X)锁

共享锁允许事务读取记录

互斥锁允许事务读写记录

这两种其实是锁的模式可以和行锁、间隙锁混搭,多个事务可以同时持有S锁,但是只有一个事务能持有X锁

意向锁

一种表锁(也是一种锁模式),表明有事务即将给对应表的记录加S或者X锁。SELECT ... LOCK IN SHARE MODE会在给记录加S锁之前先给表加IS锁,SELECT ... FOR UPDATE会在给记录加X锁之前给表加IX锁。

这是一种mysql的锁优化策略,并不是很清楚意向锁的优化点在哪里,求大佬指教

两种锁的兼容情况如下

fd01996fa16473eb5f55f5352e5bd455.png

行锁

很简单,给对应行加锁。比如update、select for update、delete等都会给涉及到的行加上行锁,防止其他事务的操作

间隙锁

在RR隔离级别下,为了防止幻读现象,除了给记录本身,还需要为记录两边的间隙加上间隙锁。

比如列a上有一个普通索引,已经有了1、5、10三条记录,select * from t where a=5 for update除了会给5这条记录加行锁,还会给间隙(1,5)和(5,10)加上间隙锁,防止其他事务插入值为5的数据造成幻读。

当a上的普通索引变成唯一索引时,不需要间隙锁,因为值唯一,select * from t where a=5 for update不可能读出两条记录来。

间隙锁相互兼容,因为如果互斥,事务A持有左半段(1,5),事务B持有右半段(1,10),那么当前面那个例子中a=5的记录被删除时,理论上左右两个间隙锁得合并成一个新锁(1,10),那么这个新的大范围锁属于谁呢?所以间隙锁相互兼容,不管是S间隙锁还是X间隙锁

插入意向锁

插入意向锁其实是一种特殊的间隙锁,从前面对间隙锁的描述中可以得知,两个事务在真正insert之前可以同时持有一段间隙的间隙锁,锁不住真正insert的这个动作。真正insert之前,mysql还会尝试获取对应记录的插入意向锁,表明有在间隙中插入一个值的意向。

插入意向锁和间隙锁互斥,比如事务1锁了(1,5)这个间隙,事务2就不能获取到a=3的插入意向锁,所以需要锁等待。

死锁过程分析

接下来就可以来分析前面那个例子中的死锁过程了,先看show engine innodb status

*** (1) TRANSACTION:

TRANSACTION 5967, ACTIVE 8 sec inserting

mysql tables in use 1, locked 1

LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1

MySQL thread id 9, OS thread handle 140528848688896, query id 537 192.168.128.1 root update

insert into t(a,b) values(0,'0')

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 64 page no 4 n bits 72 index uniq_a_b of table `t2`.`t` trx id 5967 lock_mode X locks gap before rec insert intention waiting

Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0

0: len 4; hex 80000001; asc ;;

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

2: len 4; hex 80000001; asc ;;

*** (2) TRANSACTION:

TRANSACTION 5968, ACTIVE 7 sec inserting

mysql tables in use 1, locked 1

3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1

MySQL thread id 8, OS thread handle 140528848484096, query id 538 192.168.128.1 root update

insert into t(a,b) values(0,'0')

*** (2) HOLDS THE LOCK(S):

RECORD LOCKS space id 64 page no 4 n bits 72 index uniq_a_b of table `t2`.`t` trx id 5968 lock_mode X locks gap before rec

Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0

0: len 4; hex 80000001; asc ;;

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

2: len 4; hex 80000001; asc ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 64 page no 4 n bits 72 index uniq_a_b of table `t2`.`t` trx id 5968 lock_mode X locks gap before rec insert intention waiting

Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0

0: len 4; hex 80000001; asc ;;

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

2: len 4; hex 80000001; asc ;;

*** WE ROLL BACK TRANSACTION (2)

session A(即TRANSACTION 5967)正在等待记录(a=1,b='1')之前的插入意向锁,session B(即TRANSACTION 5968)持有记录(a=1,b='1')之前的间隙锁,却也在等待那个插入意向锁。这说的什么玩意儿,是不是很诡异?

从头开始分析过程

A、B分别begin,开始事务

A先执行select * from t where a=0 and b='0' for update;,先加了IX锁,然后原本意图为给(0, '0')这条记录加排他行锁,但是记录不存在,所以变成了排他间隙锁(-∞,1)

B再执行select * from t where a=0 and b='0' for update;,也是先加了IX锁,因为记录不存在,所以加上了排他间隙锁(-∞,1),但是由于间隙锁相互兼容,所以没有block

A执行insert into t(a,b) values(0,'0');,这时候,要开始真正insert了,A需要获得(0,'0')上的插入意向锁,由于和B持有的(-∞,1)排他间隙锁冲突,所以锁等待,进入记录(0,'0')的锁等待队列(虽然记录并不存在)

B执行insert into t(a,b) values(0,'0');,要获取插入意向锁,发现虽然B自己是持有(-∞,1)的排他间隙锁,但是A也有,所以进入等待队列,等待A释放

叮,死锁发生

死锁信息解读

事务1(TRANSACTION 5967),等待获得锁index uniq_a_b of table t2.t trx id 5967 lock_mode X locks gap before rec insert intention waiting,即在唯一索引uniq_a_b上的插入意向锁(lock_mode X locks gap before rec insert intention)

锁的边界为

0: len 4; hex 80000001; asc ;;

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

2: len 4; hex 80000001; asc ;;

表明两行记录

0和1表示uniq_a_b上的值,a=1,b=0x31(即'1'的ascii码)

a=1,b='1'对应的主键id=1,因为innodb的索引结构决定的,二级索引(非主键索引)指向主键索引,主键索引再指向数据,所以需要给主键加索引

至于int值按位或上的0x80000000就不是很清楚为什么了,需要大佬解读

事务2(TRANSACTION 5968),持有间隙锁index uniq_a_b of table t2.t trx id 5968 lock_mode X locks gap before rec,等待插入意向锁index uniq_a_b of table t2.t trx id 5968 lock_mode X locks gap before rec insert intention,所以死锁发生。

原则上是innodb引擎判断哪个事务回滚代价小就回滚哪个事务,但是具体评判标准不是很清楚(再一次需要大佬),这里innodb选择了回滚事务2。至此,死锁过程分析完毕

One More Thing

还没完。。。有个神奇的现象是,如果表结构变成

create table t (

id int not null primary key AUTO_INCREMENT,

a int not null default 0,

b varchar(10) not null default '',

c varchar(10) not null default '',

unique key uniq_c(c),

unique key uniq_a_b(a,b)

);

insert into t(a,b,c) values(1,1,1);

只是把c上的唯一索引uniq_c放到了uniq_a_b前面,那么最后的死锁信息就变了!

*** (1) TRANSACTION:

TRANSACTION 5801, ACTIVE 5 sec inserting

mysql tables in use 1, locked 1

LOCK WAIT 4 lock struct(s), heap size 1136, 3 row lock(s), undo log entries 1

MySQL thread id 5, OS thread handle 140528848688896, query id 380 192.168.128.1 root update

insert into t2(a,b) values(0,'0')

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 56 page no 5 n bits 72 index uniq_a_b of table `t2`.`t2` trx id 5801 lock_mode X locks gap before rec insert intention waiting

Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0

0: len 4; hex 80000001; asc ;;

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

2: len 4; hex 80000001; asc ;;

*** (2) TRANSACTION:

TRANSACTION 5802, ACTIVE 4 sec inserting

mysql tables in use 1, locked 1

3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1

MySQL thread id 6, OS thread handle 140528848484096, query id 381 192.168.128.1 root update

insert into t2(a,b) values(0,'0')

*** (2) HOLDS THE LOCK(S):

RECORD LOCKS space id 56 page no 5 n bits 72 index uniq_a_b of table `t2`.`t2` trx id 5802 lock_mode X locks gap before rec

Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0

0: len 4; hex 80000001; asc ;;

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

2: len 4; hex 80000001; asc ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 56 page no 4 n bits 72 index uniq_c of table `t2`.`t2` trx id 5802 lock mode S waiting

Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

0: len 0; hex ; asc ;;

1: len 4; hex 80000002; asc ;;

*** WE ROLL BACK TRANSACTION (2)

事务2等待的锁由前面的插入意向锁变成了共享锁。什么鬼?

由于没看过源码,只能根据现象倒推:因为表结构上c的唯一索引在(a,b)前面,而插入的时候没指定c的值,用的默认值0,innodb需要先去查一下有没有0这条记录,有的话就要报唯一键冲突了,所以先要加S锁,但是在(0,'0')这条记录上已经有了IX锁,看一下前面的兼容性矩阵,S锁和IX锁互斥,所以也只能锁等待

总结

看似一句简单的select和insert,底下设计非常复杂的锁机制,理解这些锁机制有利于写出高效的SQL(至少是正确的😂)

遗留问题:

意向锁的优化点是哪

锁信息里,行记录按位或上的0x80000000是啥

锁互斥的判定顺序,场景1中,(0,'0')上有兼容的间隙锁,也有等待队列中的锁,先判定哪个?

innodb计算事务回滚代价的算法

参考资料

http://hedengcheng.com/?p=771

https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html#innodb-insert-intention-locks

https://dev.mysql.com/doc/refman/5.7/en/innodb-next-key-locking.html

https://dev.mysql.com/doc/refman/5.7/en/innodb-information-schema-understanding-innodb-locking.html

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对脚本之家的支持。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/562361.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于java ssm springboot女士电商平台系统源码+文档设计

作者主页:Java李杨勇 简介:Java领域优质创作者、【Java李杨勇】公号作者 简历模板、学习资料、面试题库、技术互助【关注我,都给你】 文末获取源码 主要技术:spring, springmvc, springboot,mybatis, jquery , md5 ,bootstarp.…

基于java ssm springboot网上蛋糕商城项目设计和实现

🍅 作者主页:Java李杨勇 🍅 文末获取源码联系方式📝 主要技术:spring、 springmvc、 springboot、 mybatis 、 jquery 、 md5 、bootstarp.js tomcat、富文本编译器、拦截器等 主要功能:登录、注册、商品浏览、…

基于Java springboot+vue+redis前后端分离家具商城平台系统设计和实现

主要技术实现:spring、 springmvc、 springboot、 mybatis 、 jquery 、 md5 、bootstarp.js tomcat、富文本编译器、拦截器等 主要设计功能:用户登录、注册、商城分类、商品浏览、查看、购物车、订单、支付、以及后台的管理 4.3用户需求分析 用户…

jq 修改swal的标题_js-jquery-SweetAlert2【一】使用

一、下载安装地址:https://github.com/limonte/sweetalert2二、页面引用当然还有jquery三、示例3.1、基础结构window.οnlοadfunction(){swal("Here‘s a message!");//以下代码主要修改这里}3.2、精简用法1、标题【alert】-swal(string)swal("Here…

基于Java springmvc+mybatis酒店信息管理系统设计和实现

🍅 作者主页:Java李杨勇 🍅 简介:Java领域优质创作者🏆、【java李杨勇】公号作者✌ 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 临近学期结束&#xff…

基于JAVA SSM springboot实现的抗疫物质信息管理系统设计和实现

🍅 作者主页:Java李杨勇 🍅 简介:Java领域优质创作者🏆、【java李杨勇】公号作者✌ 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 临近学期结束&#xff…

基于java springmvc+mybatis学生考试系统设计和实现

🍅 作者主页:Java李杨勇 🍅 简介:Java领域优质创作者🏆、【java李杨勇】公号作者✌ 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 临近学期结束&#xff…

java文件编译为class文件需要键入什么命令_cmd命令行 编译Java 文件

【问题引入】 很多时候,需要用到命令行来进行Java文件的编译。在用习惯了IDE 可能会不清楚如何在cmd命令行窗口进行编译,简单的都还好,带有包路径的可能会让很多人觉得有点小头疼。最近试了一下,发现了问题,就研究整理了一下记录吧。 【注1】环境准备, JDK,classpath 都…

基于JAVA springboot+mybatis智慧生活分享平台设计和实现

🍅 作者主页:Java李杨勇 🍅 简介:Java领域优质创作者🏆、Java李杨勇公号作者✌ 简历模板、学习资料、面试题库、技术互助【关注我,都给你】 🍅 欢迎点赞 👍 收藏 ⭐留言 &#x1f…

springboot自动装配的原理你能说出来吗?

SpringMvc和SpringBoot对比 首先我们回顾下原来搭建一个springmvc的hello-word的web项目(xml配置的)我们是不是要在pom中导入各种依赖,然后各个依赖有可能还会存在版本冲突需要各种排除。当你历尽千辛万苦的把依赖解决了,然后还需…

基于javaweb宠物领养平台管理系统设计和实现

🍅 作者主页:Java李杨勇 🍅 简介:Java领域优质创作者🏆、【java李杨勇】公号作者✌ 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 临近学期结束&#xff…

mysql 事务sqlserver_SQLServer数据库:事务与隔离级别实例讲解

本文主要向大家介绍了SQLServer数据库:事务与隔离级别实例讲解,通过具体的内容向大家展现,希望对大家学习SQLServer数据库有所帮助。上班途中,你在一处ATM机前停了下来。正当你在敲入密码的时候,你的一位家人也正在镇上…

基于 java springboot+mybatis二手物品网站系统设计和实现

🍅 作者主页:Java李杨勇 🍅 简介:Java领域优质创作者🏆、Java李杨勇公号作者✌ 简历模板、学习资料、面试题库、技术互助【关注我,都给你】 🍅 欢迎点赞 👍 收藏 ⭐留言 &#x1f…

AndroidStudio无法新建Java工程解决办法

我用的 AS 版本是 Android Studio Giraffe | 2022.3.1 Build #AI-223.8836.35.2231.10406996, built on June 29, 2023 以往新建工程都是 New project >> Empty Activity , 有个选择 Java 还是 Kotlin 语言的选项, 之后会默认生成一个 MainActi…

基于java springmvc+mybatis酒水商城管理系统设计和实现

🍅 作者主页:Java李杨勇 🍅 简介:Java领域优质创作者🏆、【java奥斯卡】公号作者✌ 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 临近学期结束&#xff…

基于java springboot+mybatis爱游旅行平台前台+后台设计实现

我剑最帅 不接受反驳 ​ 项目意义: 改革开放以来, 我国的旅游业发展迅速,但比较而言,我国旅游业发展的广度和深度都远远不能满足经济发展和人民生活水平提高的需要。 随着市场经济的发展和人民收入水平的进一步提高&#xff0c…

《零基础》MySQL 超级入门教程

老规矩 先上镇楼图 MySQL简介 1、什么是数据库 ? 数据库(Database)是按照数据结构来组织、存储和管理数据的仓库,它产生于距今六十多年前,随着信息技术和市场的发展,特别是二十世纪九十年代以后&#xff…

bootstarp js设置列隐藏_Bootstrap框架----DataTables列表移动端适配定义隐藏列

我们在上一章节中已经学习了DataTables在BootStrap框架中的使用方式和初始化。Bootstrap框架—-DataTables列表示例最终效果如图:Bootstrap是自动适配移动端的,在手机上查看效果如图:我们发现当数据有很多列时,会存在超出屏幕的可能。这时候表…

《零基础》MySQL 创建数据表(九)

创建MySQL数据表需要以下信息: 表名表字段名定义每个表字段 语法 以下为创建MySQL数据表的SQL通用语法: CREATE TABLE table_name (column_name column_type); 以下例子中我们将在 RUNOOB 数据库中创建数据表runoob_tbl: CREATE TABLE IF…

基于Java jsp+servlet超市订单管理平台设计和实现【建议收藏】

🍅 作者主页:Java李杨勇 🍅 简介:Java领域优质创作者🏆、Java李杨勇公号作者✌ 简历模板、学习资料、面试题库、技术互助【关注我,都给你】 🍅 欢迎点赞 👍 收藏 ⭐留言 &#x1f…