MySQL加锁策略详解
我们主要从三个方面来讨论这个问题:
- 啥时候加?
- 如何加?
- 什么时候该加什么时候不该加?
1、啥时候加

1.1 显式锁
MySQL 的加锁可以分为显式加锁和隐式加锁,显式加锁我们比较好识别的,因为他往往直接体现在 SQL 中,常见的显式加锁语句主要有:
▶︎ select ... for update;
▶︎ select ... in share mode;
两者的区别在于前者加的是排它锁,后者加的是共享锁。加了排他锁之后,后续对该范围数据的写和读操作都将被阻塞,另外一个共享锁不会阻塞读取,而是阻塞写入,但是这往往会带来一些问题,比如电商场景下更新库存时候,我们为了保障数据的一致性更新往往需要先将该商品数据锁住,如果此时两个线程并发更新库存,就可能会导致数据更新出现异常。
所以我们在业务上往往会使用 select … for update 对数据进行加锁。另外还有些咱们比较不常用的加锁方式,比如:
- 全局锁:Flush tables with read lock,主要在进行逻辑备份的时候会用到
- 表锁:lock tables … read/write
1.2 隐式锁
隐式锁是我们需要特别关注的,很多的“坑”就是因为隐式锁的存在导致的,无形往往最为致命。
表级锁除了表锁以外,还有元数据锁:
▶︎ 在进行增删改查的时候会加 MDL 读锁;
▶︎ 在对表结构进行变更的时候,会加 MDL 写锁;
这个会带来的问题就是当我们想给表添加索引或者修改表结构的时候,由于加了 MDL 写锁,会阻塞我们线上正常的读写请求,这个时候可能会触发上游的失败重试机制,那很可能就会出现请求雪崩导致 DB 被打挂。
另外的就是与我们日常业务息息相关的行锁以及间隙锁,当我们在进行增删改的时候,会根据当前的隔离级别加上行锁或者间隙锁,那么这时候需要注意是否会影响正常业务的读写性能,另外带来的风险就是可能出现加锁范围过大阻塞请求,并触发上游重试,导致服务雪崩,DB 打挂。
1.3 会不会加锁呢?
谈到这里有的同学可能有疑问,你这增删改都加锁了,那我读的时候岂不是性能很差,特别是在读多写多的业务场景下,我的读请求一上来的话,DB 不是分分钟被我查挂了?其实这里 innodb 引擎用到了一个 mvcc 的技术即多版本并发控制,其原理就是在数据更新的同时在 undolog 中记录更新的事务 id 以及相应的数据,并且维护一个 Readview 的活跃事务 id,这样当一个事务执行的时候,很容易能知道自己能看见什么数据,不能看见什么数据,这时候读取数据自然也就不会受到锁的影响能够正常地读取啦。
2、怎么加

这里讨论怎么加其实就是了解加锁的类型以及范围,即用了什么锁且加在哪里了?在讨论这个问题之前我们先来看看事务隔离级别:
▶︎ 读未提交;
▶︎ 读已提交;
▶︎ 可重复读;
▶︎ 串行化;
为啥要说这个呢?因为隔离级别也影响着咱们的加锁,读已提交解决了脏读的问题,但是未解决幻读问题;可重复读通过引入间隙锁解决了幻读问题,因此意味着不同的隔离级别用到的锁还不一样,但是有一点明确的是,越高隔离级别锁的使用更加严格。可重复读是默认的事务隔离级别,但是线上设置的隔离级别往往都是读已提交,主要是因为这个级别够用并且能够有更好的并发性能。接下来我们讨论的范围也主要是在读已提交(RC)和可重复读(RR)。
这里根据相应规则来具体分析:
▶︎ 原则1:加锁的基本单位是 next-key lock。希望你还记得,next-key lock 是前开后闭区间。
▶︎ 原则2:查找过程中访问到的对象才会加锁。
▶︎ 优化1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。
▶︎ 优化2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。
▶︎ 一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。
另外有两点需要注意的是:
▶︎ 锁是加在索引上的;
▶︎ gap锁是共享的而非独占的。
2.1 RC
接下来分别进行讨论,可能有些冗长,需要你耐心看完。
首先是 RC 级别,这个级别下的加锁规则是比较简单的,因为只涉及到行锁,首先我们先设计一张表
CREATE TABLE `t_db_lock` (`id` int(11) NOT NULL,`a` int(11) DEFAULT NULL,`b` int(11) DEFAULT NULL,PRIMARY KEY (`id`),KEY `a` (`a`)
) ENGINE=InnoDB;
insert into t_db_lock values(0,0,0),(5,5,5),(10,10,10);
2.2 主建等值存在

▶︎ 可以看到此时 sessionA 在做主键上的数据更新,将当前的记录的主键值更新为1,此时 db 会在 id=1 和 0 上加上行锁,即此时针对该id的更新会被阻塞;
▶︎ 因此当 sessionB 想插入 id=1 的记录时会被阻塞住;
▶︎ 但是由于 sessionC 更新的是 id=5 的记录,因此可以执行成功。
2.3 非唯一等值

▶︎ sessionA 根据普通索引的判断条件更新数据,由于行锁是加在索引上,因此这时候 a 列相关索引数据上了锁;
▶︎ 但是为啥这时候我更新 id=0 的数据也被阻塞了呢?因为这时除了加 a 上的索引,还有回表更新的操作,此时访问到的主键上的索引也会被加锁,因为是同一行,所以此时更新同样被阻塞住;
▶︎ 同样的道理,当我们去更新的 b=0 的数据对应的主键索引上也是同一条数据,所以此时更新也被阻塞,但是如果我们此时是更新 b=5 的这条数据的话就能更新成功。
2.4 主键等值不存在

▶︎ sessionA 加了一个 id 为2的锁,此时这行记录不存在,行锁没有加成功,因此不会阻塞其他 session 的请求;
▶︎ sessionB 执行成功;
▶︎ sessionC 执行成功。
2.5 无索引等值不存在

▶︎ 这种情况和主键等值不存在一致,由于未找到对应的加锁记录,则后续的更新操作都能够执行成功。
2.6 主键范围

▶︎ sessionA 根据范围加锁,锁了 id=0 和 5 这两行数据;
▶︎ sessionB 由于更新 id=0 这行已经上锁的数据,所以被阻塞住;
▶︎ sessionC 由于之前 id=1 这行记录并不存在,所以可以正常插入,这个场景是不是有点熟悉,就是咱们所说的幻读,如果这时候在 sessionA 中再执行 select * from t_db_lock where id >= 0 and id <= 5 就会发现多了一条数据;
2.7 RR
这里可重复读级别下主要是讨论间隙锁的加锁场景,这种加锁情况会比读已提交的隔离级别复杂的多;set session transaction isolation level repeatable read。
2.8 主键等值存在

▶︎ sessionA 在已经存在的 id=5 这行加锁,根据加锁规则,唯一索引会退化为行锁,因此仅在 id=5 这行加锁;其实这也好理解,既然已经是唯一索引了,那么就不会会出现幻读的情况,因此幻读仅仅取决于这行是否存在,因此我只要给该行加锁保证不再写入即可;
▶︎ sessionB 和 sessionC 均不在锁范围内则插入成功.
2.9 非唯一等值

▶︎ sessionA 在已经存在的 a=5 这行记录上加锁,由于是非唯一索引,根据加锁规则,首先扫描 a 索引加上 next-key lock (0,5] ,接着向右遍历到第一个不满足条件的(根据规则五,唯一索引上的范围查询会访问到不满足条件的第一个值为止),并退化为间隙锁,因此加锁范围为(5,10),总体加锁范围为(0,10);并且 for update,也会对应在主键的索引范围内加上锁,即(0,10);
▶︎ sessionB 在主键索引的锁范围内,因此被阻塞;
▶︎ sessionC 此时不在普通索引和主键索引的范围上,因此执行成功;
这里可以看到,对于非唯一等值查询的情况下,加锁的范围要比主键等值存在更大,因此我们在对非唯一索引加锁的时候需要注意这个范围。
2.10 主键等值不存在

▶︎ sessionA 此时对 id=3 的记录加上了行锁,但是由于此时3这行的记录不存在,会对此范围加锁,按照加锁原则,向右遍历且最后一个值不满足等值条件,next-key lock 退化为间隙锁,此时加锁范围为(0,5);
▶︎ sessionB 属于加锁范围内,因此被阻塞;
▶︎ sessionC 不在此加锁范围内,加锁成功。
为啥这里要加的是范围锁呢,其实主要解决的是幻读问题,假设这里没有在此范围内加锁,那么 T1 时刻 sessionB 执行成功,T2 时刻再次执行 select * from t_db_lock where id = 3 的话,就会发现原先查询不到的结果现在竟然可以查询到了,就像出现幻觉一样;为了避免出现这种幻读的情况,需要在此范围内加锁。
2.11 非唯一等值不存在

▶︎ sessionA 在 a=3 这行上加锁的,由于 db 中不存在该行,所以同样会加next-key lock,并且因为锁都是加在索引上的,因此会在 a 索引上加上(0,5)的范围锁。但是这里有个奇怪的现象,当 a=5 时,如果 id<5 会阻塞,如果 id>5 则会成功,从结果看来,此时 a 上的锁似乎是有偏向性的,并不是严格意义上的 a=5 时就会锁住相应的插入记录
2.12 主键范围

▶︎ sessionA 进行范围查询加锁,在语义上等价于 select * from t_db_lock where id = 5 for update,但是实际加锁情况还是有很大的区别,首先 id >= 5 根据等值查询查询到id=5这行加锁为(0,5],由于是唯一索引,退化为行锁,因此在 id=5 这行上加了锁,接着向右查询,找到第一个不满足条件的值,即 id=10 这行,所以加 next-key lock(5,10],这里因为并不是等值查询,不会有退化为间隙锁的过程,所以整体加锁范围[5,10];
▶︎ sessionB 不在锁范围内,插入成功;
▶︎ sessionC 在锁中,插入失败,注意这里是被阻塞住,而不是报主键冲突。
2.13 非唯一范围

▶︎ sessionA 加锁范围区别于主键索引主要是在(0, 5]这个范围下并未退化为行锁,因此总体加锁范围为(0, 10]
2.14 无索引等值不存在

▶︎ sessionA 中加锁记录为 b=6 这行,由于 b 未创建索引,因此会将所有 b 索引上的记录都加锁,由于是 for update 加锁,认为还回去主表上更新,因此主表的相关记录也都被上了锁,这就会导致加锁期间处于锁表的状态,任何的更新操作都没办法成功,这在线上会是非常危险的操作,可能会导致 db 被打垮。
3、什么时候该加什么时候不该加
通过上述的分析我们应该对锁的类型以及语句中加锁的范围有一个大致的了解,可以知道悲观锁是需要我们谨慎使用的,因为很可能简单的 SQL 就会拖垮 db 的性能,影响线上服务的质量,那么什么时候该加什么时候不该加呢?
我认为对于 db 的并发场景,我们可以这么去考虑:
▶︎ 尽可能优先考虑使用乐观锁的方式解决;
▶︎ 如果需要用到悲观锁,则务必在加锁的键上加索引;
▶︎ 确认 db 的隔离级别,分析 SQL 中可能存在导致冲突或者死锁的原因,避免 SQL 被长时间阻塞;
其实对于 db 的互斥方案并没有银弹,要根据具体的业务场景去针对性的制定解决方案,只是在可能出现的一些坑中,我们能够提前识别到,避免低级错误,并且有能力去优化他,这就是能让自己不断进步提升的好方法啦。
学习资料:点此下载
相关文章:
MySQL加锁策略详解
我们主要从三个方面来讨论这个问题: 啥时候加?如何加?什么时候该加什么时候不该加? 1、啥时候加 1.1 显式锁 MySQL 的加锁可以分为显式加锁和隐式加锁,显式加锁我们比较好识别的,因为他往往直接体现在 S…...
会声会影2024新功能及剪辑视频步骤教程
会声会影2024的新功能主要包括: 全新的标题动态与特效:用户可以为文字标题指定进入、中场和退出的不同动态效果,比如闪现进入、中场弹跳和淡出退出等,让文字标题更具动感。此外,还新增了多个标题特效,包括…...
Rust Vs Go:从头构建一个web服务
Go 和 Rust 之间的许多比较都强调它们在语法和初始学习曲线上的差异。然而,最终的决定性因素是重要项目的易用性。 “Rust 与 Go”争论 Rust vs Go 是一个不断出现的话题,并且已经有很多关于它的文章。部分原因是开发人员正在寻找信息来帮助他们决定下…...
几个常见的C/C++语言冷知识
当涉及到C/C语言时,有一些冷知识可能并不为人所熟知,但却可以让你更深入地理解这门古老而强大的编程语言。以下是一些有趣的C/C语言冷知识。 1. 数组的下标可以是负数 在我们日常的C语言编程中,数组是一个非常常见的数据结构。我们习惯性地使…...
低代码开发:学校低成本数字化转型的新引擎
随着科技的飞速发展,数字化转型已经成为教育行业的一大趋势。然而,对于许多学校来说,高昂的数字化改造成本成为了一道难以逾越的门槛。本文将探讨如何通过低代码开发,以低成本实现学校数字化转型,为教育行业注入新活力…...
【es6】解决箭头函数所有的问题,箭头函数的 this 指针,使用 new 操作符
箭头函数是 es6 提出的this 指针指向 定义箭头函数的全局上下文或函数上下文,是固定的call\apply\bind无法改变箭头函数的this没有 arguments 对象,但是可以访问外部作用域中的 arguments 对象没有 prototype 属性不能用作 generator 函数,不…...
2-1SDN(软件定义网络)环境测试实验(无默认控制器)-静态流表
控制器是为了更好集中控制网络的行为,当然在实际中我们可以根据需要选择是否选择控制器,或是结合ovs交换机的特点进行针对性的转发行为设置(功能需要书写代码进行软件定义),转发功能需要手工添加流表才能实现 如&#…...
飞天使-k8s知识点22-kubernetes实操7-ingress
文章目录 ingress环境准备准备service和pod验证效果 https 代理效果 ingress 在 Kubernetes 中,Ingress 是一种 API 对象,它管理外部访问集群内部服务的规则。你可以将其视为一个入口,它可以将来自集群外部的 HTTP 和 HTTPS 路由到集群内部的…...
SwiftUI 集合视图(Grid)拖放交换 Cell 的极简实现
概览 自从 SwiftUI 横空出世那天起,小伙伴们都感受到了它惊人的简单与便捷。而在本课中,我们将会用一个小“栗子”更直观的让大家体验到它无与伦比简洁的描述性特质: 如上图所示,我们在 SwiftUI 中实现了 Grid 中拖放交换 Cell 的…...
MATLAB中gtext函数用法
目录 语法 说明 示例 使用鼠标将文本添加到图窗 指定字体大小和颜色 在创建后修改文本 gtext函数的功能是使用鼠标将文本添加到图窗。 语法 gtext(str) gtext(str,Name,Value) t gtext(___) 说明 gtext(str) 在使用鼠标选择的位置插入文本 str。当将鼠标指针悬停在图…...
迁移SVN和GIT的云端数据
在新服务器搭建GIT仓库 教程很多,大致的流程是: 1. 新建linux用户密码专用于git操作 2. 新建git库的存放文件夹并在此初始化git 3. 配置git库所在目录权限 *只需要有一个库和有一个用户,与在windows上建库是一样的。不需要搭建类似gitla…...
算法--动态规划(背包问题)
这里写目录标题 总览dp问题的优化01背包问题概述算法思想算法思想中的注意点例题代码 完全背包问题概述 多重背包问题概述 分组背包问题概述 总览 dp问题的优化 要清楚:dp问题的优化一般是对dp问题的代码或者计算方程做一个等效变形 有了这个前提,我们在…...
Word 文档中的图片另存为 .jpg 格式图片
Word 文档中的图片另存为 .jpg 格式图片 1. Office 按钮 -> 另存为2. 筛选过的网页 (*.htm;*.html)3. 查看生成文件夹References 1. Office 按钮 -> 另存为 2. 筛选过的网页 (*.htm;*.html) 3. 查看生成文件夹 References [1] Yongqiang Cheng, https://yongq…...
【C++练级之路】【Lv.8】【STL】list类的模拟实现
快乐的流畅:个人主页 个人专栏:《C语言》《数据结构世界》《进击的C》 远方有一堆篝火,在为久候之人燃烧! 文章目录 引言一、结点二、迭代器2.1 成员变量与默认成员函数2.2 operator*2.3 operator->2.4 operator2.5 operator- …...
【右一的电子笔记】全导航,持续更新...
文章目录 📚计算机基础🐇高程(c)🐇python基础🐇数据结构🐇数据库系统概念🐇计算机网络🐇计算机组成原理🐇操作系统 📚大数据🐇大数据管…...
关于前端的console的方法的收集
console的常用方法列举 console.assert() 如果第一个参数为 false ,则将消息和堆栈跟踪记录到控制台。 console.clear() 清空控制台,并输出 Console was cleared。 console.count() 以参数为标识记录调用的次数,调用时在控制台打印标识…...
大工程 从0到1 数据治理 数仓篇(sample database classicmodels _No.7)
大工程 从0到1 数据治理 之数仓篇 我这里还是sample database classicmodels为案列,可以下载,我看 网上还没有类似的 案列,那就 从 0-1开始吧! 提示:写完文章后,目录可以自动生成,如何生成可参…...
phpcms v9敏感词内容替换
后台先在"扩展"——>"敏感词管理"中添加敏感词,然后修改phpcms\modules\content\content.php文件来实现添加或者编辑内容时敏感词的替换。(如果涉及会员投稿和留言等,也需要在对应模块中做类似处理) 在ad…...
浏览器---浏览器/http相关面试题
1.localStorage和sessionStorage 共同点:二者都是以key-value的键值对方式存储在浏览器端,大小大概在5M。 区别: (1)数据有效期不同:sessionStorage仅在当前浏览器窗口关闭之前有效;localStorag…...
java 中开源的html解析库Jsoup 简单例子
下面是一个使用Jsoup库解析HTML的简单Java例子。这个例子展示了如何使用Jsoup从一个HTML字符串中提取数据。 首先,确保你已经将Jsoup作为依赖项添加到你的项目中。如果你使用的是Maven,可以在pom.xml文件中添加以下依赖: &…...
MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...
USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...
云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...
MySQL 主从同步异常处理
阅读原文:https://www.xiaozaoshu.top/articles/mysql-m-s-update-pk MySQL 做双主,遇到的这个错误: Could not execute Update_rows event on table ... Error_code: 1032是 MySQL 主从复制时的经典错误之一,通常表示ÿ…...
【LeetCode】算法详解#6 ---除自身以外数组的乘积
1.题目介绍 给定一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O…...
