当前位置: 首页 > news >正文

MySQL加锁策略详解

我们主要从三个方面来讨论这个问题:

  • 啥时候加?
  • 如何加?
  • 什么时候该加什么时候不该加?

1、啥时候加

image.png

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、怎么加

image.png

这里讨论怎么加其实就是了解加锁的类型以及范围,即用了什么锁且加在哪里了?在讨论这个问题之前我们先来看看事务隔离级别:

▶︎ 读未提交;

▶︎ 读已提交;

▶︎ 可重复读;

▶︎ 串行化;

为啥要说这个呢?因为隔离级别也影响着咱们的加锁,读已提交解决了脏读的问题,但是未解决幻读问题;可重复读通过引入间隙锁解决了幻读问题,因此意味着不同的隔离级别用到的锁还不一样,但是有一点明确的是,越高隔离级别锁的使用更加严格。可重复读是默认的事务隔离级别,但是线上设置的隔离级别往往都是读已提交,主要是因为这个级别够用并且能够有更好的并发性能。接下来我们讨论的范围也主要是在读已提交(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 主建等值存在

image.png

▶︎ 可以看到此时 sessionA 在做主键上的数据更新,将当前的记录的主键值更新为1,此时 db 会在 id=1 和 0 上加上行锁,即此时针对该id的更新会被阻塞;

▶︎ 因此当 sessionB 想插入 id=1 的记录时会被阻塞住;

▶︎ 但是由于 sessionC 更新的是 id=5 的记录,因此可以执行成功。

2.3 非唯一等值

image.png

▶︎ sessionA 根据普通索引的判断条件更新数据,由于行锁是加在索引上,因此这时候 a 列相关索引数据上了锁;

▶︎ 但是为啥这时候我更新 id=0 的数据也被阻塞了呢?因为这时除了加 a 上的索引,还有回表更新的操作,此时访问到的主键上的索引也会被加锁,因为是同一行,所以此时更新同样被阻塞住;

▶︎ 同样的道理,当我们去更新的 b=0 的数据对应的主键索引上也是同一条数据,所以此时更新也被阻塞,但是如果我们此时是更新 b=5 的这条数据的话就能更新成功。

2.4 主键等值不存在

image.png

▶︎ sessionA 加了一个 id 为2的锁,此时这行记录不存在,行锁没有加成功,因此不会阻塞其他 session 的请求;

▶︎ sessionB 执行成功;

▶︎ sessionC 执行成功。

2.5 无索引等值不存在

image.png

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

2.6 主键范围

image.png

▶︎ 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 主键等值存在

image.png

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

▶︎ sessionB 和 sessionC 均不在锁范围内则插入成功.

2.9 非唯一等值

image.png

▶︎ sessionA 在已经存在的 a=5 这行记录上加锁,由于是非唯一索引,根据加锁规则,首先扫描 a 索引加上 next-key lock (0,5] ,接着向右遍历到第一个不满足条件的(根据规则五,唯一索引上的范围查询会访问到不满足条件的第一个值为止),并退化为间隙锁,因此加锁范围为(5,10),总体加锁范围为(0,10);并且 for update,也会对应在主键的索引范围内加上锁,即(0,10);

▶︎ sessionB 在主键索引的锁范围内,因此被阻塞;

▶︎ sessionC 此时不在普通索引和主键索引的范围上,因此执行成功;

这里可以看到,对于非唯一等值查询的情况下,加锁的范围要比主键等值存在更大,因此我们在对非唯一索引加锁的时候需要注意这个范围。

2.10 主键等值不存在

image.png

▶︎ sessionA 此时对 id=3 的记录加上了行锁,但是由于此时3这行的记录不存在,会对此范围加锁,按照加锁原则,向右遍历且最后一个值不满足等值条件,next-key lock 退化为间隙锁,此时加锁范围为(0,5);

▶︎ sessionB 属于加锁范围内,因此被阻塞;

▶︎ sessionC 不在此加锁范围内,加锁成功。

为啥这里要加的是范围锁呢,其实主要解决的是幻读问题,假设这里没有在此范围内加锁,那么 T1 时刻 sessionB 执行成功,T2 时刻再次执行 select * from t_db_lock where id = 3 的话,就会发现原先查询不到的结果现在竟然可以查询到了,就像出现幻觉一样;为了避免出现这种幻读的情况,需要在此范围内加锁。

2.11 非唯一等值不存在

image.png

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

2.12 主键范围

image.png

▶︎ 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 非唯一范围

image.png

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

2.14 无索引等值不存在

image.png

▶︎ sessionA 中加锁记录为 b=6 这行,由于 b 未创建索引,因此会将所有 b 索引上的记录都加锁,由于是 for update 加锁,认为还回去主表上更新,因此主表的相关记录也都被上了锁,这就会导致加锁期间处于锁表的状态,任何的更新操作都没办法成功,这在线上会是非常危险的操作,可能会导致 db 被打垮。

3、什么时候该加什么时候不该加

通过上述的分析我们应该对锁的类型以及语句中加锁的范围有一个大致的了解,可以知道悲观锁是需要我们谨慎使用的,因为很可能简单的 SQL 就会拖垮 db 的性能,影响线上服务的质量,那么什么时候该加什么时候不该加呢?

我认为对于 db 的并发场景,我们可以这么去考虑:

▶︎ 尽可能优先考虑使用乐观锁的方式解决;

▶︎ 如果需要用到悲观锁,则务必在加锁的键上加索引;

▶︎ 确认 db 的隔离级别,分析 SQL 中可能存在导致冲突或者死锁的原因,避免 SQL 被长时间阻塞;

其实对于 db 的互斥方案并没有银弹,要根据具体的业务场景去针对性的制定解决方案,只是在可能出现的一些坑中,我们能够提前识别到,避免低级错误,并且有能力去优化他,这就是能让自己不断进步提升的好方法啦。

学习资料:点此下载

相关文章:

MySQL加锁策略详解

我们主要从三个方面来讨论这个问题&#xff1a; 啥时候加&#xff1f;如何加&#xff1f;什么时候该加什么时候不该加&#xff1f; 1、啥时候加 1.1 显式锁 MySQL 的加锁可以分为显式加锁和隐式加锁&#xff0c;显式加锁我们比较好识别的&#xff0c;因为他往往直接体现在 S…...

会声会影2024新功能及剪辑视频步骤教程

会声会影2024的新功能主要包括&#xff1a; 全新的标题动态与特效&#xff1a;用户可以为文字标题指定进入、中场和退出的不同动态效果&#xff0c;比如闪现进入、中场弹跳和淡出退出等&#xff0c;让文字标题更具动感。此外&#xff0c;还新增了多个标题特效&#xff0c;包括…...

Rust Vs Go:从头构建一个web服务

Go 和 Rust 之间的许多比较都强调它们在语法和初始学习曲线上的差异。然而&#xff0c;最终的决定性因素是重要项目的易用性。 “Rust 与 Go”争论 Rust vs Go 是一个不断出现的话题&#xff0c;并且已经有很多关于它的文章。部分原因是开发人员正在寻找信息来帮助他们决定下…...

几个常见的C/C++语言冷知识

当涉及到C/C语言时&#xff0c;有一些冷知识可能并不为人所熟知&#xff0c;但却可以让你更深入地理解这门古老而强大的编程语言。以下是一些有趣的C/C语言冷知识。 1. 数组的下标可以是负数 在我们日常的C语言编程中&#xff0c;数组是一个非常常见的数据结构。我们习惯性地使…...

低代码开发:学校低成本数字化转型的新引擎

随着科技的飞速发展&#xff0c;数字化转型已经成为教育行业的一大趋势。然而&#xff0c;对于许多学校来说&#xff0c;高昂的数字化改造成本成为了一道难以逾越的门槛。本文将探讨如何通过低代码开发&#xff0c;以低成本实现学校数字化转型&#xff0c;为教育行业注入新活力…...

【es6】解决箭头函数所有的问题,箭头函数的 this 指针,使用 new 操作符

箭头函数是 es6 提出的this 指针指向 定义箭头函数的全局上下文或函数上下文&#xff0c;是固定的call\apply\bind无法改变箭头函数的this没有 arguments 对象&#xff0c;但是可以访问外部作用域中的 arguments 对象没有 prototype 属性不能用作 generator 函数&#xff0c;不…...

2-1SDN(软件定义网络)环境测试实验(无默认控制器)-静态流表

控制器是为了更好集中控制网络的行为&#xff0c;当然在实际中我们可以根据需要选择是否选择控制器&#xff0c;或是结合ovs交换机的特点进行针对性的转发行为设置&#xff08;功能需要书写代码进行软件定义&#xff09;&#xff0c;转发功能需要手工添加流表才能实现 如&#…...

飞天使-k8s知识点22-kubernetes实操7-ingress

文章目录 ingress环境准备准备service和pod验证效果 https 代理效果 ingress 在 Kubernetes 中&#xff0c;Ingress 是一种 API 对象&#xff0c;它管理外部访问集群内部服务的规则。你可以将其视为一个入口&#xff0c;它可以将来自集群外部的 HTTP 和 HTTPS 路由到集群内部的…...

SwiftUI 集合视图(Grid)拖放交换 Cell 的极简实现

概览 自从 SwiftUI 横空出世那天起&#xff0c;小伙伴们都感受到了它惊人的简单与便捷。而在本课中&#xff0c;我们将会用一个小“栗子”更直观的让大家体验到它无与伦比简洁的描述性特质&#xff1a; 如上图所示&#xff0c;我们在 SwiftUI 中实现了 Grid 中拖放交换 Cell 的…...

MATLAB中gtext函数用法

目录 语法 说明 示例 使用鼠标将文本添加到图窗 指定字体大小和颜色 在创建后修改文本 gtext函数的功能是使用鼠标将文本添加到图窗。 语法 gtext(str) gtext(str,Name,Value) t gtext(___) 说明 gtext(str) 在使用鼠标选择的位置插入文本 str。当将鼠标指针悬停在图…...

迁移SVN和GIT的云端数据

在新服务器搭建GIT仓库 教程很多&#xff0c;大致的流程是&#xff1a; 1. 新建linux用户密码专用于git操作 2. 新建git库的存放文件夹并在此初始化git 3. 配置git库所在目录权限 *只需要有一个库和有一个用户&#xff0c;与在windows上建库是一样的。不需要搭建类似gitla…...

算法--动态规划(背包问题)

这里写目录标题 总览dp问题的优化01背包问题概述算法思想算法思想中的注意点例题代码 完全背包问题概述 多重背包问题概述 分组背包问题概述 总览 dp问题的优化 要清楚&#xff1a;dp问题的优化一般是对dp问题的代码或者计算方程做一个等效变形 有了这个前提&#xff0c;我们在…...

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类的模拟实现

快乐的流畅&#xff1a;个人主页 个人专栏&#xff1a;《C语言》《数据结构世界》《进击的C》 远方有一堆篝火&#xff0c;在为久候之人燃烧&#xff01; 文章目录 引言一、结点二、迭代器2.1 成员变量与默认成员函数2.2 operator*2.3 operator->2.4 operator2.5 operator- …...

【右一的电子笔记】全导航,持续更新...

文章目录 &#x1f4da;计算机基础&#x1f407;高程&#xff08;c&#xff09;&#x1f407;python基础&#x1f407;数据结构&#x1f407;数据库系统概念&#x1f407;计算机网络&#x1f407;计算机组成原理&#x1f407;操作系统 &#x1f4da;大数据&#x1f407;大数据管…...

关于前端的console的方法的收集

console的常用方法列举 console.assert() 如果第一个参数为 false &#xff0c;则将消息和堆栈跟踪记录到控制台。 console.clear() 清空控制台&#xff0c;并输出 Console was cleared。 console.count() 以参数为标识记录调用的次数&#xff0c;调用时在控制台打印标识…...

大工程 从0到1 数据治理 数仓篇(sample database classicmodels _No.7)

大工程 从0到1 数据治理 之数仓篇 我这里还是sample database classicmodels为案列&#xff0c;可以下载&#xff0c;我看 网上还没有类似的 案列&#xff0c;那就 从 0-1开始吧&#xff01; 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参…...

phpcms v9敏感词内容替换

后台先在"扩展"——>"敏感词管理"中添加敏感词&#xff0c;然后修改phpcms\modules\content\content.php文件来实现添加或者编辑内容时敏感词的替换。&#xff08;如果涉及会员投稿和留言等&#xff0c;也需要在对应模块中做类似处理&#xff09; 在ad…...

浏览器---浏览器/http相关面试题

1.localStorage和sessionStorage 共同点&#xff1a;二者都是以key-value的键值对方式存储在浏览器端&#xff0c;大小大概在5M。 区别&#xff1a; &#xff08;1&#xff09;数据有效期不同&#xff1a;sessionStorage仅在当前浏览器窗口关闭之前有效&#xff1b;localStorag…...

java 中开源的html解析库Jsoup 简单例子

下面是一个使用Jsoup库解析HTML的简单Java例子。这个例子展示了如何使用Jsoup从一个HTML字符串中提取数据。 首先&#xff0c;确保你已经将Jsoup作为依赖项添加到你的项目中。如果你使用的是Maven&#xff0c;可以在pom.xml文件中添加以下依赖&#xff1a; &…...

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;KubeSphere 容器平台高可用&#xff1a;环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

MMaDA: Multimodal Large Diffusion Language Models

CODE &#xff1a; https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA&#xff0c;它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构&#xf…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文全面剖析RNN核心原理&#xff0c;深入讲解梯度消失/爆炸问题&#xff0c;并通过LSTM/GRU结构实现解决方案&#xff0c;提供时间序列预测和文本生成…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅!

【把数组变成一棵树】有序数组秒变平衡BST,原来可以这么优雅! 🌱 前言:一棵树的浪漫,从数组开始说起 程序员的世界里,数组是最常见的基本结构之一,几乎每种语言、每种算法都少不了它。可你有没有想过,一组看似“线性排列”的有序数组,竟然可以**“长”成一棵平衡的二…...

React核心概念:State是什么?如何用useState管理组件自己的数据?

系列回顾&#xff1a; 在上一篇《React入门第一步》中&#xff0c;我们已经成功创建并运行了第一个React项目。我们学会了用Vite初始化项目&#xff0c;并修改了App.jsx组件&#xff0c;让页面显示出我们想要的文字。但是&#xff0c;那个页面是“死”的&#xff0c;它只是静态…...

无需布线的革命:电力载波技术赋能楼宇自控系统-亚川科技

无需布线的革命&#xff1a;电力载波技术赋能楼宇自控系统 在楼宇自动化领域&#xff0c;传统控制系统依赖复杂的专用通信线路&#xff0c;不仅施工成本高昂&#xff0c;后期维护和扩展也极为不便。电力载波技术&#xff08;PLC&#xff09;的突破性应用&#xff0c;彻底改变了…...

Linux入门课的思维导图

耗时两周&#xff0c;终于把慕课网上的Linux的基础入门课实操、总结完了&#xff01; 第一次以Blog的形式做学习记录&#xff0c;过程很有意思&#xff0c;但也很耗时。 课程时长5h&#xff0c;涉及到很多专有名词&#xff0c;要去逐个查找&#xff0c;以前接触过的概念因为时…...

【AI News | 20250609】每日AI进展

AI Repos 1、OpenHands-Versa OpenHands-Versa 是一个通用型 AI 智能体&#xff0c;通过结合代码编辑与执行、网络搜索、多模态网络浏览和文件访问等通用工具&#xff0c;在软件工程、网络导航和工作流自动化等多个领域展现出卓越性能。它在 SWE-Bench Multimodal、GAIA 和 Th…...

生成对抗网络(GAN)损失函数解读

GAN损失函数的形式&#xff1a; 以下是对每个部分的解读&#xff1a; 1. ⁡, ​ &#xff1a;这个部分表示生成器&#xff08;Generator&#xff09;G的目标是最小化损失函数。 &#xff1a;判别器&#xff08;Discriminator&#xff09;D的目标是最大化损失函数。 GAN的训…...