记一次线上问题引发的对 Mysql 锁机制分析
背景
最近双十一开门红期间组内出现了一次因 Mysql 死锁导致的线上问题,当时从监控可以看到数据库活跃连接数飙升,导致应用层数据库连接池被打满,后续所有请求都因获取不到连接而失败
整体业务代码精简逻辑如下:
@Transaction
public void service(Integer id) {delete(id);insert(id);
}
数据库实例监控:
当时通过分析上游问题流量限流解决后,后续找时间又重新分析了下问题发生的根本原因,现将其总结如下:本篇文章会先对 Mysql 中的各种锁进行分析,包括互斥锁、间隙锁和插入意向锁,让大家对各种锁的使用场景有一个了解,然后在此基础上再对本问题进行分析,希望大家未来再碰到相似场景时,能够快速的定位问题
Mysql 锁机制
在 Mysql 中为了解决对同一行记录并发写的问题,引入了行锁机制,多个事务不能同时对一行数据进行修改操作,当需要对数据库中的一行数据进行修改时,会首先判断该行数据是否加锁,如果没加锁,那么当前事务加锁成功,可以进行后续的修改操作;但如果该行数据已经被其他事务加锁,则当前事务只有等待加锁的事务释放锁后才能加锁成功,继续执行修改操作
本篇文章中所有实验用到的建表语句:
create table `test` (`id` int(11) NOT NULL,`num` int(11) NOT NULL,PRIMARY KEY (`id`),KEY `num` (`num`)
) ENGINE = InnoDB;insert intotest
values
(10, 10),
(20, 20),
(30, 30),
(40, 40),
(50, 50);
Shared and Exclusive Locks
shared(S) lock 表示共享锁,当一个事务持有某行上的 S 锁后可以对该行的数据进行读操作,通过语句 select ... from test lock in share mode 可以添加共享锁,一般使用的较少,不做过多阐述
exclusive(X) lock 表示互斥锁,当一个事务对某行数据进行 update 或 delete 操作时都要先获取到该记录上的 X 锁,如果已经有其他事务获取到了该记录上的 X 锁,那么当前事务会阻塞等待直到上一事务释放了对应记录上的 X 锁
S 锁之间不互斥,多个事务可以同时获取一条记录上的 S 锁 X 锁之间互斥,多个事务不能同时获取同一条记录上的 X 锁 S 锁和 X 锁之间互斥,多个事务不能同时获取同一条记录上的 S 锁和 X 锁
当多个事务同时去 update 索引上同一条记录时,都需要先获取到该记录上的 X 锁,所谓的锁也就是会在内存中生成一个数据结构来记录当前的事务信息、锁类型和是否等待等信息。下图中就是 T1 和 T2 同时去更新 id = 30 的这行记录,并且 T1 成功获取到了锁,其在内存中生成的锁结构信息中字段 is_wating 为 false,可以继续执行事务的后续逻辑,而 T2 获取锁失败,则生成的锁结构信息字段 is_wating 为 true,阻塞等待 T1 上的锁释放
互斥锁在 Mysql 日志中的锁信息为:lock_mode X locks rec but not gap
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10078 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 00: len 4; hex 8000000a; asc ;;1: len 6; hex 00000000274f; asc 'O;;2: len 7; hex b60000019d0110; asc ;;
Gap Locks
上一小节中介绍了 Exclusive Locks,该锁可以避免多个事务同时对一行记录进行更新操作,但不能解决幻读的问题,所谓的幻读就是指一个事务在前后两次查询同一个范围时,后一次查询到了前一次没有的记录
| session A | session B |
T1 | select num from test where num > 10 and num < 15 for update; (0 rows) | |
T2 | | insert into test values(12, 12); |
T3 | select num from test where num > 10 and num < 15 for update; (1 rows) | |
在上面这个场景中,session A 分别在 T1、T3 时刻进行了两次范围查询,session B 在 T2 时刻插入了一条该范围内的数据,如果 session A 能在 T3 时刻查询出 session B 插入的数据,就说明发生了幻读。此时只使用互斥锁是无法解决幻读的,因为 num = 12 的记录在数据库中还不存在,不能给其加上互斥锁来防止 T2 时刻 session B 的插入
因此为了解决幻读问题,只有引入新的锁机制,也就是间隙锁(Gap Locks)。间隙锁和互斥锁不同,互斥锁是行锁,只会锁定一行特定的记录,而间隙锁则是锁定两行记录之间的空隙,防止其他事务在此间隙中插入新的记录
引入了间隙锁之后,session A 在 T1 时刻会给 id = 20 记录生成一个 Gap Locks,之后 session B 在 T2 时刻想要插入记录时,需要先判断待插入位置的后一条记录上是否存在 Gap Locks,很明显此时 id = 20 的记录上已经存在了 Gap Locks,那么session B 就需要在 id = 20 的记录上生成一个插入意向锁,并进入锁等待
间隙锁在 Mysql 中的锁日志信息如下:lock_mode X locks gap before rec
RECORD LOCKS space id 133 page no 3 n bits 80 index PRIMARY of table `test`.`test` trx id 38849 lock_mode X locks gap before rec
Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 00: len 4; hex 8000001e; asc 30 ;;1: len 6; hex 00000000969c; asc ;;2: len 7; hex a60000011a0128; asc (;;3: len 4; hex 8000001e; asc ;;
间隙锁虽然解决了幻读问题,但因每次都会锁住一段间隙,大大降低了数据库整体的并发度,且因间隙锁和间隙锁之间不互斥,不同事务可以同时对同一间隙加上 Gap Locks,这也往往是各种死锁产生的源头
Next-Key Locks
Next-Key Locks 是 (Shard/Exclusive Locks + Gap Locks) 的结合,当 session A 给某行记录 R 添加了互斥型的 Next-Key Locks 后, 相当于拥有了记录 R 的 X 锁和记录 R 的 Gap Locks
在上面 Gap Locks 的例子中事务 1 加的就是 Next-Key Locks,即同时给 id = 20 的记录加了 X 锁和 Gap 锁
在可重复读隔离级别下,update 和 delete 操作默认都会给记录添加 Next-Key Locks,Mysql 中 Next-Key Locks 的锁日志信息为:lock_mode X
RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10080 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 00: len 8; hex 73757072656d756d; asc supremum;;Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 00: len 4; hex 8000000a; asc ;;1: len 6; hex 00000000274f; asc 'O;;2: len 7; hex b60000019d0110; asc ;
Insert Intention Locks
插入意向锁(Insert Intention Locks) 也是一种间隙锁,由 INSERT 操作在行数据插入之前获取
在插入一条记录前,需要先定位到该记录在 B+ 树中的存储位置,然后判断待插入位置的下一条记录上是否添加了 Gap Locks,如果下一条记录上存在 Gap Locks,那么插入操作就需要阻塞等待,直到拥有 Gap Locks 的那个事务提交,同时执行插入操作等待的事务也会在内存中生成一个锁结构,表明有事务想在某个间隙中插入新记录,但目前处于阻塞状态,生成的锁结构就是插入意向锁
实验模拟如下:
| session 1 | session 2 | session 3 |
T1 | begin; | | |
T2 | select * from test where id = 25 for update; | | |
T3 | | insert into test values(26, 26); (blocked) | |
T4 | | | insert into test values(26, 26); (blocked) |
对于语句 select * from test where id = 25 for update 因当前表中不存在该记录,在可重复读隔离级别下,为了避免幻读,会给 (20, 30] 间隙加上 Gap Locks
从锁日志可以看出 session 1 给记录 30 添加了间隙锁(lock_mode X locks gap before rec)
RECORD LOCKS space id 133 page no 3 n bits 80 index PRIMARY of table `test`.`test` trx id 38849 lock_mode X locks gap before rec
Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 00: len 4; hex 8000001e; asc 30 ;;1: len 6; hex 00000000969c; asc ;;2: len 7; hex a60000011a0128; asc (;;3: len 4; hex 8000001e; asc ;;
当 session 2 插入记录 26 时,会在 B+ 树中先定位到待插入位置,再判断插入位置的间隙是否存在 Gap Locks,也就是判断待插入位置的后一记录 id = 30 是否存在 Gap Locks,如果存在需要在该记录上生成插入意向锁等待
RECORD LOCKS space id 133 page no 3 n bits 80 index PRIMARY of table `test`.`test` trx id 38850 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 4; compact format; info bits 00: len 4; hex 8000001e; asc 30 ;;1: len 6; hex 00000000969c; asc ;;2: len 7; hex a60000011a0128; asc (;;3: len 4; hex 8000001e; asc ;;
此时 session 2 和 session 3 都在 id = 30 的记录上添加了插入意向锁等待 session 1 上的 Gap Locks 释放,生成的锁记录如下:
线上问题分析
在对 Mysql 中的各种锁结构有了一个清晰的了解之后,回过头来再看看前面的线上问题
@Transaction
public void service(Integer id) {delete(id);insert(id);
}
对于上面的业务代码可能存在下面两种情况:
•传入的参数 id 在原数据库中不存在
•传入的参数 id 在原数据库中存在
本次主要会针对 id 记录在原数据库中不存在进行分析
| session 1 | session 2 | session 3 |
T1 | delete from test where id = 15; | | |
T2 | | delete from test where id = 15; | delete from test where id = 15; |
T3 | insert into test values(15, 15); | | |
T4 | | insert into test values(15, 15); | |
T5 | | | insert into test values(15, 15); |
因 id = 15 在数据库中不存在,在 T1 时刻 session 1 会给其所在间隙的下一条记录添加上 Gap Locks,又因 Gap Locks 不互斥, 在 T2 时刻 session 2 和session 3 都会同时获取到 id = 20 的 Gap 锁
下图中 tx: T1、T2、T3 分别代表 session 1、session 2 和 session 3
当在 T3 时刻 session 1 插入 id = 15 的记录时,会判断其插入位置的后一条记录是否存在 Gap Locks,如果存在,则需要在该记录上生成 Insert Intention Locks 并等待持有 Gap Locks 的事务释放锁
在 T4 时刻 session 2 执行插入语句,同样会因插入位置的后一条记录中存在 Gap Locks 而需要生成 Insert Intention Locks 等待。此时很明显就形成了死锁,session 1 生成插入意向锁等待 session 2 和 session 3 上的 Gap 锁释放,而 session 2 同样生成插入意向锁等待 session 1 和 session 3 上的 Gap 锁释放
在 T4 时刻检测到死锁后,Mysql 会选择其中一个事务进行回滚,假设此时 session 2 被回滚,释放了其持有的所有锁资源,session 1 可以继续执行吗? 很明显不可以,session 1 还同时在等待 session 3 上的 Gap 锁释放,继续阻塞等待
在 T5 时刻 session 3 开始执行插入语句,此时同 T4 时刻,死锁形成,session 1 生成的插入意向锁正在等待 session 3 上的 Gap Locks 释放,session 3 上生成的插入意向锁正在等待 session 1 上的 Gap Locks 释放,此时 session 3 回滚释放所有锁资源后,session 1 才可以最终执行成功
在完成了三个并发线程的死锁分析后,可能有人会想虽然有死锁,但通过死锁检测可以很快的检测出,程序也可以正常的执行,这有什么问题呢? 其实上面没有问题主要是因为并发量较小,死锁检测可以很快检测出,如果此时将并发量扩大 100 倍甚至 1000 倍后,还会没有问题吗?
看看当时出现线上问题时,接口的调用量情况,
进一步在本地模拟 300 个线程并发执行,因人脑并发分析所有事务的执行情况的话会非常复杂,本次只以事务 1 为一个点来进行分析
从图中可以看到当 T1 在执行插入语句时,需要等待 T2- T101 上持有的 Gap Locks 释放,之后 T2 - T6 可能同时执行插入语句,然后进行死锁检测,事务回滚,看着似乎只要后续有事务执行了插入语句就会执行死锁回滚,正常运行,但在死锁检测的过程中还会有新事务(T101 - T 200 )获取到 Gap Locks,造成锁等待队列中的事务越来越多,而 Mysql 的整体死锁检测时间复杂度为 O(n^2),锁等待队列中的事务较多时,每一次有新事务进行锁等待,死锁检测都需要遍历锁等待队列中在其之前等待的事务,判断是否会因自己的加入形成环,此时检测会非常消耗 CPU 资源,造成数据库整体性能下降,死锁检测耗时增加,Mysql 活跃连接数大幅增加,并且因锁等待而连接无法释放,最终造成应用层连接池被打满
综上分析,本次出现问题的最主要原因是在短时间内存在大并发的请求对同一行数据进行先删除再插入操作(先更新再插入同理),造成了死锁等待,应用层连接池被打满,大量上游请求超时重试,进一步导致锁等待,最终影响了所有依赖该数据库的业务
因此对于未来在业务代码中存在相似逻辑的地方,一定要做好防重校验,避免短时间内存在对同一行数据的先更新再插入的并发操作。同时在可重复读隔离别下,更新和删除操作默认都会添加 Next-Key Locks,间隙锁的引入使得死锁问题在并发情况下很容易出现,这也是在业务逻辑实现上需要考虑的问题。
总结
本文以一个线上问题为背景,对 Mysql 中的各种锁机制进行了详细的总结,分析了各个锁的加锁时机和具体使用场景,其中特别要注意间隙锁的使用,因间隙锁和间隙锁之间不互斥,当多个事务之间并发执行时很容易形成死锁
相关文章:

记一次线上问题引发的对 Mysql 锁机制分析
背景 最近双十一开门红期间组内出现了一次因 Mysql 死锁导致的线上问题,当时从监控可以看到数据库活跃连接数飙升,导致应用层数据库连接池被打满,后续所有请求都因获取不到连接而失败 整体业务代码精简逻辑如下: Transaction p…...

Android 工厂模式距离传感器逻辑优化
Android 工厂模式距离传感器逻辑优化 接到客户反馈提到距离传感器校准完毕之后,每次测试完成界面都会弹出“请点击校准按钮进行校准!”Toast弹窗,需要对弹窗的显示逻辑进行优化,即只让其在首次进入距离传感器测试界面时弹出&#…...

Dell笔记本电脑 启动时提示解决
https://www.dell.com/support/kbdoc/en-us/000139731/what-the-headless-operation-mode-active-post-message-means-and-how-to-stop-it-appearing-during-start-up dell官方解释: 提示来自于BIOS/UEFI固件中POST Behaviar,只要打开了忽略警告、错误…...

【人工智能Ⅰ】7-KNN 决策树
【人工智能Ⅰ】7-KNN & 决策树 7-1 KNN(K near neighbour) 思想:一个样本与数据集中的k个样本最相似,若这k个样本大多数属于某类别,则该个样本也属于这类别 距离度量 样本相似性用欧氏距离定义 L p ( x i , x…...

【LeetCode】26. 删除有序数组中的重复项
26. 删除有序数组中的重复项 难度:简单 题目 给你一个 非严格递增排列 的数组 nums ,请你原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素…...

K8S知识点(八)
(1)实战入门-Label 通过标签实现Pod的区分,说白了就是一种标签选择机制 可以使用命令是否加了标签: 打标签: 更新标签: 筛选标签: 修改配置文件,重新创建一个pod 筛选࿱…...

25.4 MySQL 函数
1. 函数的介绍 1.1 函数简介 在编程中, 函数是一种组织代码的方式, 用于执行特定任务. 它是一段可以被重复使用的代码块, 通常接受一些输入(参数)然后返回一个输出. 函数可以帮助开发者将大型程序分解为更小的, 更易于管理的部分, 提高代码的可读性和可维护性.函数在编程语言…...

Unity 下载Zip压缩文件并且解压缩
1、Unity下载Zip压缩文件主要使用UnityWebRequest类。 可以参考以下方法: webRequest UnityWebRequest.Get(Path1); //压缩文件路径webRequest.timeout 60;webRequest.downloadHandler new DownloadHandlerBuffer();long fileSize GetLocalFileSize(Path2); …...

c++11新特性篇-委托构造函数和继承构造函数
C11引入了委托构造函数(Delegating Constructor)和C11及后续标准引入了继承构造函数(Inheriting Constructor)两个特性。 1.委托构造函数 委托构造函数是C11引入的一个特性,它允许一个构造函数调用同一类的另一个构造…...

Flink SQL处理回撤流(Retract Stream)案例
Flink SQL支持处理回撤流(Retract Stream),下面是一个使用Flink SQL消费回撤流的案例: 假设有一个数据流,包含用户的姓名和年龄,希望计算每个姓名的年龄总和。 以下是示例代码: // 创建流执行…...

6.5.事件图层(MapEventsOverlay)
愿你出走半生,归来仍是少年! 简单来说就是一个不参与绘制但是可进行交互的图层,它具备了单击和长按的交互功能。 booleanonSingleTapConfirmed(MotionEvent e, MapView mapView)booleanonLongPress(MotionEvent e, MapView mapView) 通过继承它重写上方…...

供暖系统如何实现数据远程采集?贝锐蒲公英高效实现智慧运维
山西某企业专注于暖通领域,坚持为城市集中供热行业和楼宇中央空调行业提供全面、专业的“智慧冷暖”解决方案。基于我国供热行业的管理现状,企业成功研发并推出了可将能源供应、管理与信息化、自动化相融合的ICS-DH供热节能管理系统。 但是,由…...

Flutter笔记:关于Flutter中的大文件上传(上)
Flutter笔记 关于Flutter中的大文件上传(上) 大文件上传背景与 Flutter 端实现文件分片传输 作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263 邮箱 :291148484163.com 本文地址&#…...

腾讯云CVM服务器5年可选2核4G和4核8G配置
腾讯云服务器网整理五年云服务器优惠活动 txyfwq.com/go/txy 配置可选2核4G和4核8G,公网带宽可选1M、3M或5M,系统盘为50G高性能云硬盘,标准型S5实例CPU采用主频2.5GHz的Intel Xeon Cascade Lake或者Intel Xeon Cooper Lake处理器,…...

数据结构:反射
基本概念 反射中的四个类 Class类 Java文件在被编译之后,生成了.class文件,JVM此时解读.class文件,将其解析为java.lang.Class 对象,在程序运行时每个java文件就最终变成了Class类对象的一个实例。通过反射机制应用这个 实例就…...

45 深度学习(九):transformer
文章目录 transformer原理代码的基础准备位置编码Encoder blockmulti-head attentionFeed Forward自定义encoder block Deconder blockEncoderDecodertransformer自定义loss 和 学习率mask生成函数训练翻译 transformer 这边讲一下这几年如日中天的新的seq2seq模式的transform…...

java中用javax.servlet.ServletInputStream.readLine有什么安全问题吗?怎么解决实例?
使用 javax.servlet.ServletInputStream.readLine 方法在处理 Servlet 请求时可能存在以下安全问题,以及相应的解决方案: 缓冲区溢出:readLine 方法会将数据读取到一个缓冲区中,并根据换行符分隔成行。如果输入流中包含过长的行或…...

面试官问 Spring AOP 中两种代理模式的区别?很多面试者被问懵了
面试官问 Spring AOP 中两种代理模式的区别?很多初学者栽了跟头,快来一起学习吧! 代理模式是一种结构性设计模式。为对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象,并允许在将请求提交给对象前后进行一…...

MQ四大消费问题一锅端:消息不丢失 + 消息积压 + 重复消费 + 消费顺序性
RabbitMQ-如何保证消息不丢失 生产者把消息发送到 RabbitMQ Server 的过程中丢失 从生产者发送消息的角度来说,RabbitMQ 提供了一个 Confirm(消息确认)机制,生产者发送消息到 Server 端以后,如果消息处理成功ÿ…...

Python爬虫——入门爬取网页数据
目录 前言 一、Python爬虫入门 二、使用代理IP 三、反爬虫技术 1. 间隔时间 2. 随机UA 3. 使用Cookies 四、总结 前言 本文介绍Python爬虫入门教程,主要讲解如何使用Python爬取网页数据,包括基本的网页数据抓取、使用代理IP和反爬虫技术。 一、…...

爬虫,TLS指纹 剖析和绕过
当你欲爬取某网页的信息数据时,发现通过浏览器可正常访问,而通过代码请求失败,换了随机ua头IP等等都没什么用时,有可能识别了你的TLS指纹做了验证。 解决办法: 1、修改 源代码 2、使用第三方库 curl-cffi from curl…...

Linux shell编程学习笔记25:tty
1 tty的由来 在 1830 年代和 1840 年代,开发了称为电传打字机(teletypewriters)的机器,这些机器可以将发件人在键盘上输入的消息“沿着线路”发送在接收端并打印在纸上。 电传打字机的名称由teletypewriters, 缩短为…...

AIGC大模型-初探
大语⾔模型技术链 1. ⾃然语⾔处理 2. 神经⽹络 3. ⾃注意⼒机制 4. Transformer 架构 5. 具体模型 - GPT6. 预训练,微调 7. ⼤模型应⽤ - LangChain 大语⾔模型有什么用? 利⽤⼤语⾔模型帮助我们理解⼈类的命令,从⽽处理⽂本分析…...

Postman for Mac(HTTP请求发送调试工具)v10.18.10官方版
Postman for mac是一个提供在MAC设备上功能强大的开发,监控和测试API的绝佳工具。非常适合开发人员去使用。此版本通过Interceptor添加了对请求捕获的支持,修正了使用上下文菜单操作未复制响应正文的问题和预请求脚本的垂直滚动条与自动完成下拉列表重叠…...

SpringBoot 项目优雅实现读写分离 | 京东云技术团队
一、读写分离介绍 当使用Spring Boot开发数据库应用时,读写分离是一种常见的优化策略。读写分离将读操作和写操作分别分配给不同的数据库实例,以提高系统的吞吐量和性能。 读写分离实现主要是通过动态数据源功能实现的,动态数据源是一种通过…...

企业如何利用好用户画像对客户进行精准营销?提高营销转化?
随着市场竞争的加剧,企业对于客户的需求和行为越来越关注,如何利用好用户画像对客户进行精准营销,提高营销转化,成为企业关注的焦点。 一、了解用户需求和行为 首先,企业需要了解客户的需求和行为,包括客户…...

acwing算法基础之搜索与图论--匈牙利算法求二分图的最大匹配数
目录 1 基础知识2 模板3 工程化 1 基础知识 二分图中的最大匹配数:从二分图中选择一些边(这些边连接集合A和集合B,集合A中结点数目为n1,集合B中结点数目为n2),设为集合S,其中任意两条边不共用一…...

优化重复冗余代码的8种方式
文章目录 前言1、抽取公用方法2、抽工具类3、反射4、泛型5、继承与多态6、使用设计模式7、自定义注解(或者说AOP面向切面)8、函数式接口和Lambda表达式 前言 日常开发中,我们经常会遇到一些重复代码。大家都知道重复代码不好,它主要有这些缺点ÿ…...

DVWA - 3
文章目录 XSS(Dom)lowmediumhighimpossible XSS(Dom) XSS 主要基于JavaScript语言进行恶意攻击,常用于窃取 cookie,越权操作,传播病毒等。DOM全称为Document Object Model,即文档对…...

android studio离线tips
由于种种原因(你懂的,导致我们使用android studio会有很多坑,这里记录一下遇到的问题以及解决方案 环境问题 无法下载gradle 因为android studio采用gradle作为构建工具,国内gradle没有镜像下载非常慢,并且大概率失…...