面试官:怎么解决MySQL中的死锁问题?

咱们使用 MySQL 大概率上都会遇到死锁问题,这实在是个令人非常头痛的问题。本文将会对死锁进行相应介绍,对常见的死锁案例进行相关分析与探讨,以及如何去尽可能避免死锁给出一些建议。

话不多说,开整!

什么是死锁

死锁是并发系统中常见的问题,同样也会出现在数据库MySQL的并发读写请求场景中。当两个及以上的事务,双方都在等待对方释放已经持有的锁或因为加锁顺序不一致造成循环等待锁资源,就会出现“死锁”。常见的报错信息为 Deadlock found when trying to get lock...

举例来说 A 事务持有 X1 锁 ,申请 X2 锁,B事务持有 X2 锁,申请 X1 锁。A 和 B 事务持有锁并且申请对方持有的锁进入循环等待,就造成了死锁。

9fd9b0300c731c34e59584b1b059db59.png

如上图,是右侧的四辆汽车资源请求产生了回路现象,即死循环,导致了死锁。

从死锁的定义来看,MySQL 出现死锁的几个要素为:

  1. 两个或者两个以上事务

  2. 每个事务都已经持有锁并且申请新的锁

  3. 锁资源同时只能被同一个事务持有或者不兼容

  4. 事务之间因为持有锁和申请锁导致彼此循环等待

InnoDB 锁类型

为了分析死锁,我们有必要对 InnoDB 的锁类型有一个了解。ba8392a0341f6b6e925f604691e5daa9.png

MySQL InnoDB 引擎实现了标准的行级别锁:共享锁( S lock ) 和排他锁 ( X lock )

  1. 不同事务可以同时对同一行记录加 S 锁。

  2. 如果一个事务对某一行记录加 X 锁,其他事务就不能加 S 锁或者 X 锁,从而导致锁等待。

如果事务 T1 持有行 r 的 S 锁,那么另一个事务 T2 请求 r 的锁时,会做如下处理:

  1. T2 请求 S 锁立即被允许,结果 T1 T2 都持有 r 行的 S 锁

  2. T2 请求 X 锁不能被立即允许

如果 T1 持有 r 的 X 锁,那么 T2 请求 r 的 X、S 锁都不能被立即允许,T2 必须等待 T1 释放 X 锁才可以,因为 X 锁与任何的锁都不兼容。共享锁和排他锁的兼容性如下所示:937acda74a4f72a8197b393799da8b17.png

间隙锁( gap lock )

间隙锁锁住一个间隙以防止插入。假设索引列有2, 4, 8 三个值,如果对 4 加锁,那么也会同时对(2,4)和(4,8)这两个间隙加锁。其他事务无法插入索引值在这两个间隙之间的记录。但是,间隙锁有个例外:

  1. 如果索引列是唯一索引,那么只会锁住这条记录(只加行锁),而不会锁住间隙。

  2. 对于联合索引且是唯一索引,如果 where 条件只包括联合索引的一部分,那么依然会加间隙锁。

next-key lock

next-key lock 实际上就是 行锁+这条记录前面的 gap lock 的组合。假设有索引值10,11,13和 20,那么可能的 next-key lock 包括:

(负无穷,10],(10,11],(11,13],(13,20],(20,正无穷)

在 RR 隔离级别下,InnoDB 使用 next-key lock 主要是防止幻读问题产生。

意向锁( Intention lock )

InnoDB 为了支持多粒度的加锁,允许行锁和表锁同时存在。为了支持在不同粒度上的加锁操作,InnoDB 支持了额外的一种锁方式,称之为意向锁( Intention Lock )。意向锁是将锁定的对象分为多个层次,意向锁意味着事务希望在更细粒度上进行加锁。意向锁分为两种:

  1. 意向共享锁( IS ):事务有意向对表中的某些行加共享锁

  2. 意向排他锁( IX ):事务有意向对表中的某些行加排他锁

由于 InnoDB 存储引擎支持的是行级别的锁,因此意向锁其实不会阻塞除全表扫描以外的任何请求。表级意向锁与行级锁的兼容性如下所示:b2200a4459afd1f224f082141b6c49a8.png

插入意向锁( Insert Intention lock )

插入意向锁是在插入一行记录操作之前设置的一种间隙锁,这个锁释放了一种插入方式的信号,即多个事务在相同的索引间隙插入时如果不是插入间隙中相同的位置就不需要互相等待。假设某列有索引值2,6,只要两个事务插入位置不同(如事务 A 插入3,事务 B 插入4),那么就可以同时插入。

锁模式兼容矩阵

横向是已持有锁,纵向是正在请求的锁:95aa6e8e84cfee504562bd47d38a9d97.png

阅读死锁日志

在进行具体案例分析之前,咱们先了解下如何去读懂死锁日志,尽可能地使用死锁日志里面的信息来帮助我们来解决死锁问题。

后面测试用例的数据库场景如下:MySQL 5.7 事务隔离级别为 RR

表结构和数据如下:1a6f2af61b78d31ab446d2539705fef0.png

测试用例如下:60389580f5eb1cfa96da86ea86ef5ba0.png

通过执行show engine innodb status 可以查看到最近一次死锁的日志。

日志分析如下:

  1. ***** (1) TRANSACTION: TRANSACTION 2322, ACTIVE 6 sec starting index read

事务号为2322,活跃 6秒,starting index read 表示事务状态为根据索引读取数据。常见的其他状态有:8cd69a6bcdbc96ffefa8d47efa717518.png

mysql tables in use 1 说明当前的事务使用一个表。

locked 1 表示表上有一个表锁,对于 DML 语句为 LOCK_IX

LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)

LOCK WAIT 表示正在等待锁,2 lock struct(s) 表示 trx->trx_locks 锁链表的长度为2,每个链表节点代表该事务持有的一个锁结构,包括表锁,记录锁以及自增锁等。本用例中 2locks 表示 IX 锁和lock_mode X (Next-key lock)

1 row lock(s) 表示当前事务持有的行记录锁/ gap 锁的个数。

MySQL thread id 37, OS thread handle 140445500716800, query id 1234 127.0.0.1 root updating

MySQL thread id 37 表示执行该事务的线程 ID 为 37 (即 show processlist; 展示的 ID )

delete from student where stuno=5 表示事务1正在执行的 sql,比较难受的事情是 show engine innodb status 是查看不到完整的 sql 的,通常显示当前正在等待锁的 sql。

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

RECORD LOCKS space id 11 page no 5 n bits 72 index idx_stuno of table cw****.****student trx id 2322 lock_mode X waiting

RECORD LOCKS 表示记录锁, 此条内容表示事务 1 正在等待表 student 上的 idx_stuno 的 X 锁,本案例中其实是 Next-Key Lock 。

事务2的 log 和上面分析类似:

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

RECORD LOCKS space id 11 page no 5 n bits 72 index idx_stuno of table cw****.****student trx id 2321 lock_mode X

显示事务 2 的 insert into student(stuno,score) values(2,10) 持有了 a=5 的 Lock mode X

| LOCK_gap,不过我们从日志里面看不到事务2执行的 delete from student where stuno=5;

这点也是造成 DBA 仅仅根据日志难以分析死锁的问题的根本原因。

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

RECORD LOCKS space id 11 page no 5 n bits 72 index idx_stuno of table cw****.****student trx id 2321 lock_mode X locks gap before rec insert intention waiting

表示事务 2 的 insert 语句正在等待插入意向锁 lock_mode X locks gap before rec insert intention waiting ( LOCK_X + LOCK_REC_gap )

经典案例分析

案例一:事务并发 insert 唯一键冲突

表结构和数据如下所示:feed0aec51e7d57b5519571093bea1ea.png58160ca25a847ad4472874ffa1940ed3.png测试用例如下:047ac737e0ddbe6a88817a3bc51c823c.png日志分析如下:

  1. 事务 T2 insert into t7(id,a) values (26,10) 语句 insert 成功,持有 a=10 的 排他行锁( Xlocks rec but no gap )

  2. 事务 T1 insert into t7(id,a) values (30,10), 因为T2的第一条 insert 已经插入 a=10 的记录,事务 T1 insert a=10 则发生唯一键冲突,需要申请对冲突的唯一索引加上S Next-key Lock( 即 lock mode S waiting ) 这是一个间隙锁会申请锁住(,10],(10,20]之间的 gap 区域。

  3. 事务 T2 insert into t7(id,a) values (40,9)该语句插入的 a=9 的值在事务 T1 申请的 gap 锁4-10之间, 故需事务 T2 的第二条 insert 语句要等待事务 T1 的 S-Next-key Lock 锁释放,在日志中显示 lock_mode X locks gap before rec insert intention waiting 。

案例一:先 update 再 insert 的并发死锁问题

表结构如下,无数据:a8a71d5f63aeb8f26a64719dfbc944d1.png测试用例如下:03df36ed54a6f7c49768f1001e2039c3.png死锁分析:
可以看到两个事务 update 不存在的记录,先后获得间隙锁( gap 锁),gap 锁之间是兼容的所以在update环节不会阻塞。两者都持有 gap 锁,然后去竞争插入意向锁。当存在其他会话持有 gap 锁的时候,当前会话申请不了插入意向锁,导致死锁。

如何尽可能避免死锁

  1. 合理的设计索引,区分度高的列放到组合索引前面,使业务 SQL 尽可能通过索引定位更少的行,减少锁竞争

  2. 调整业务逻辑 SQL 执行顺序, 避免 update/delete 长时间持有锁的 SQL 在事务前面。

  3. 避免大事务,尽量将大事务拆成多个小事务来处理,小事务发生锁冲突的几率也更小。

  4. 固定的顺序访问表和行。比如两个更新数据的事务,事务 A 更新数据的顺序为 1,2;事务 B 更新数据的顺序为 2,1。这样更可能会造成死锁。

  5. 在并发比较高的系统中,不要显式加锁,特别是是在事务里显式加锁。如 select … for update 语句,如果是在事务里(运行了 start transaction 或设置了autocommit 等于0),那么就会锁定所查找到的记录。

  6. 尽量按主键/索引去查找记录,范围查找增加了锁冲突的可能性,也不要利用数据库做一些额外额度计算工作。比如有的程序会用到 “select … where … order by rand();”这样的语句,由于类似这样的语句用不到索引,因此将导致整个表的数据都被锁住。

  7. 优化 SQL 和表设计,减少同时占用太多资源的情况。比如说,减少连接的表,将复杂 SQL 分解为多个简单的 SQL。


好了。今天就说到这了,我还会不断分享自己的所学所想,希望我们一起走在成功的道路上!

b17759f4db77292dd28bdbbbab315e17.gif

往期推荐

c1acb1225414e8613928765b90cfbe05.png

1.3w字,一文详解死锁!


91dfcb8d629bd724547e6f3116752b97.png

10个经典又容易被人疏忽的JVM面试题


b0174874183dbc41f734ca9ab86a30c5.png

什么是可中断锁?有什么用?怎么实现?



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

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

相关文章

ubuntu双系统导致进windows花屏

2019独角兽企业重金招聘Python工程师标准>>> 5600U的集成显卡,装了ubuntu的双系统,居然导致进win7的时候花屏,度娘狗哥都不得求解 网上很多解决方法都说在启动时加上nomodeset,发现对ubuntu15没用,且失去了…

工作总结:日志打印的15个建议

前言 日志是快速定位问题的好帮手,是撕逼和甩锅的利器!打印好日志非常重要。今天我们来聊聊日志打印的15个好建议~1. 选择恰当的日志级别 常见的日志级别有5种,分别是error、warn、info、debug、trace。日常开发中,我们需要选择恰…

MFC属性页对话框

属性页对话框 分类 分页和引导 类 CPropertyPage-父亲CDialog类别,所谓的属性页或网页对话框。 CPropertySheet-父类是CWnd,称为属性表单。 一个完整的属性页对话框由一个属性表单多个属性页组成。属性页嵌套在属性表单内。 标签式属性页的创建步骤&…

面试官:ConcurrentHashMap为什么放弃了分段锁?

今天我们来讨论一下一个比较经典的面试题就是 ConcurrentHashMap 为什么放弃使用了分段锁,这个面试题阿粉相信很多人肯定觉得有点头疼,因为很少有人在开发中去研究这块的内容,今天阿粉就来给大家讲一下这个 ConcurrentHashMap 为什么在 JDK8 …

C语言函数指针的应用——自制谐波分析软件

文章目录函数指针简介格式介绍颜色头文件计算机仿真使用说明完整代码部分效果图函数指针简介 如果在一个大型C语言程序中要反复调用函数,而调用的函数又不明确时,函数指针就是一个非常有用的东西。如果你的函数体内可以传递不同的函数,那就非…

PHP5.5四种序列化性能对比

2019独角兽企业重金招聘Python工程师标准>>> 结论: 1、小数组用msgpack,无论空间和性能都最好 2、大数组,考虑空间用igbinary,考虑性能用msgpack json_encode,serialize,igbinary,msgpack四种序列化方式&am…

MyBatis Plus 批量数据插入功能,yyds!

作者 | 王磊来源 | Java中文社群(ID:javacn666)转载请联系授权(微信ID:GG_Stone最近 Review 小伙伴代码的时候,发现了一个小小的问题,小伙伴竟然在 for 循环中进行了 insert (插入&a…

C语言打印彩色字符——以(枚举法+字符串查找)为例展示

文章目录C语言颜色头文件——自制非常简单的调用函数实战演练——一个基础的枚举变量小程序牛刀小试——查找字符小程序C语言颜色头文件——自制非常简单的调用函数 显然,C语言是不会提供打印彩色字符的标准函数,而我们有时候为了强调C语言打印的部分字…

再见 Spring Task,这个定时任务框架真香!

最近有朋友问到定时任务相关的问题。于是,我简单写了一篇文章总结一下定时任务的一些概念以及一些常见的定时任务技术选型。希望能对小伙伴们有帮助!个人能力有限。如果文章有任何需要补充/完善/修改的地方,欢迎在评论区指出,共同…

C语言实现动画控制

文章目录原材料说明一场革命原材料 下载原材料网址: https://www.easyx.cn/downloads/ 下载easyx2014冬至版,将lib文件放在编译器默认的lib文件夹,h头文件放在编译器默认的include文件夹即可 说明 C语言可以用系统内部的定时函数sleep和usleep定时(需…

聊聊redis分布式锁的8大坑

前言在分布式系统中,由于redis分布式锁相对于更简单和高效,成为了分布式锁的首先,被我们用到了很多实际业务场景当中。但不是说用了redis分布式锁,就可以高枕无忧了,如果没有用好或者用对,也会引来一些意想…

MyBatis 批量插入数据的 3 种方法!

作者 | 王磊来源 | Java中文社群(ID:javacn666)转载请联系授权(微信ID:GG_Stone批量插入功能是我们日常工作中比较常见的业务功能之一,之前我也写过一篇关于《MyBatis Plus 批量数据插入功能,yy…

MongoDB: The Definitive Guide

第一章 简介 MongoDB是面向文档的数据库,不是关系型数据库。内置对MapReduce的支持,以及对地理空间索引的支持。 丰富的数据模型容易扩展,它所采用的面向文档的数据模型可以使其在多台服务器之间分割数据丰富的功能,索引、存储Jav…

Python联网下载文件

声明 Python版本2.7.3所需Py文件——urllib22.7.3版本的Python Shell即可直接执行,但需要联网若程序执行成功,则会下载以下网址的txt文本并打印在shell中 http://helloworldbook2.com/data/message.txt 本代码来源于《父与子的编程之旅——与小卡特一起…

如何给SpringBoot配置轻松加密?

在实践中,项目的某些配置信息是需要进行加密处理的,以减少敏感信息泄露的风险。比如,在使用Druid时,就可以基于它提供的公私钥加密方式对数据库的密码进行加密。但更多时候,比如Redis密码、MQ密码等敏感信息&#xff0…

C语言将循环小数/有限小数转换为分数

文章目录数学基础编程思路代码数学基础 早在小学的时候我就对循环小数非常感兴趣,加上初中和高中对循环小数可以说有一定基础研究,因此想到写一个将循环下小数转换为分数的程序,非常有意思,并且对初学者来说,它的输入…

升级了 Windows 11 正式版,有坑吗?

作者 | 王磊来源 | Java中文社群(ID:javacn666)转载请联系授权(微信ID:GG_Stone)今天磊哥去公司上班,惊喜的发现 Windows 提示更新了,并且是 Windows 11 正式版,这太让人…

C语言结构体的应用——万年历

文章目录万年历简述代码万年历简述 万年历——就是输入一个日期可以查询是星期几,这个功能看起来很普通,但是如果用程序时间的话,还是药费一番周折: 我们需要保存一个固定的日期,存放它是星期几,输入一个自定义的日期…

@Value竟然能玩出这么多花样

前言对于从事java开发工作的小伙伴来说,spring框架肯定再熟悉不过了。spring给开发者提供了非常丰富的api,满足我们日常的工作需求。如果想要创建bean实例,可以使用Controller、Service、Repository、Component等注解。如果想要依赖注入某个对…

C语言实现线性动态(单向)链表【详细步骤】

文章目录什么是链表为什么不用结构体数组链表的操作创建表删除元素插入元素代码及运行结果什么是链表 链表是数据结构里面的一种,线性链表是链表的一种,线性链表的延伸有双向链表和环形链表。在编程语言中优化数据结构可以在处理大数据时大大降低程序的…