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

学习资料:点此下载

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

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

相关文章

“目标检测”任务基础认识

“目标检测”任务基础认识 1.目标检测初识 目标检测任务关注的是图片中特定目标物体的位置。 目标检测最终目的&#xff1a;检测在一个窗口中是否有物体。 eg:以猫脸检测举例&#xff0c;当给出一张图片时&#xff0c;我们需要框出猫脸的位置并给出猫脸的大小&#xff0c;如…

开源 - 一款可自定义的在线免杀平台|过x60、wd等

免责声明&#xff1a;本工具仅供安全研究和教学目的使用&#xff0c;用户须自行承担因使用该工具而引起的一切法律及相关责任。作者概不对任何法律责任承担责任&#xff0c;且保留随时中止、修改或终止本工具的权利。使用者应当遵循当地法律法规&#xff0c;并理解并同意本声明…

工作经验总结:Hex文件解析

一、Hex文件简介 由一行行符合Intel HEX文件格式的文本所构成的ASCII文本文件。一般用于MCU程序烧录&#xff0c;可以把hex文件理解为带有地址信息的bin数据的记录集合。&#xff08;注&#xff1a;烧录时如果使用bin文件则需要指明对应首地址的位置&#xff0c;而使用hex文件…

最新2024FL Studio21.2.3中文免费版数字音乐工作站(DAW)

FL Studio 21作为一款功能强大的数字音乐工作站&#xff08;DAW&#xff09;&#xff0c;被广泛应用于各种音乐制作场景中。以下是一些具体的案例&#xff1a; FL Studio 21 Win-安装包下载如下: https://wm.makeding.com/iclk/?zoneid55981 FL Studio 21 Mac-安装包下载如下…

<网络安全>《45 网络攻防专业课<第十一课 - NTFS/EFS/BitLocker数据加密与解密>》

1 NTFS文件系统 磁盘加密 1.1 NTFS安全简介 NTFS是Windows 2000及之后的操作系统的标准文件系统。NTFS不仅在性能上比起FAT 32强&#xff0c;NTFS支持的最大文件大小、最大分区大小也比FAT32大&#xff0c;NTFS还支持配额、安全功能。 在NTFS分区中&#xff0c;对于某个文件或…

一个C#开发的大小只有8KB的贪吃蛇开源游戏!

大家好&#xff0c;我是编程乐趣。 今天给大家推荐基于C#开发的、一个贪吃蛇开源项目&#xff0c;这个项目除了实现贪吃蛇的功能外&#xff0c;重点是讲解如何把编译后的程序&#xff0c;从65MB精简为8KB。 项目地址 https://github.com/MichalStrehovsky/SeeSharpSnake 编译…

Java SourceDataLine 播放音频 显示频谱

Java SourceDataLine 播放MP3音频 显示频谱 1 添加依赖2 快速傅里叶变换2.1 FFT.java2.2 Complex.java 3 音频播放3.1 Player.java3.1 XPlayer.java 4 显示频谱5 结果 项目Value音频格式 添加依赖*.wav(JDK 原生支持)*.pcm(JDK 原生支持)*.au(JDK 原生支持)*.aiff(JDK 原生支持…

vue3项目引入本地js文件,实现一个音频播放按钮

目前有一个需求就是在网页上放置一个音乐控制按钮&#xff0c;并且是在vue3项目里面。于是小白的我遇到了2个问题&#xff0c;第一个问题是如何实现没有进度条的播放按钮&#xff0c;这个网上有现成的代码&#xff0c;可以通过js代码切换不同的图片或者是别的样式&#xff0c;并…

SparkSQL学习02-编程入口

文章目录 1 DataFrame的构建方式方式一&#xff1a;JavaBean反射的方式1.1 创建Scala类1.2 创建Scala对象 方式二&#xff1a;动态编码的方式 2 DataSet的构建方式3 RDD和DataFrame以及DataSet之间的相互转换3.1【RDD-->DataFrame】和【RDD-->DataSet】3.2【DataFrame--&…

java基础之 SPI机制

SPI机制说明 什么是SPI Service Provider Interface 机制是Java提供的一套用来被第三方实现或扩展的API&#xff0c;他可以用来启用框架扩展和替换组件。通过“基于接口的编程 策略模式 配置文件”组合实现的动态加载机制。SPI机制为某个接口寻找服务实现的机制&#xff0c;…

二叉树基础知识总结

目录 二叉树基础知识 概念 : 根节点的五个形态 : 特殊的二叉树 满二叉树 : 完全二叉树 : 二叉搜索树 : 平衡二叉搜索树 : 二叉树的性质 : 二叉树的存储结构 二叉树的顺序存储结构 二叉树的链式存储结构 二叉树的遍历方式 : 基础概念 前中后遍历 层序遍历 :…

【Redis】理论进阶篇------浅谈Redis的缓存穿透和雪崩原理

一、缓存穿透 1、概念 缓存穿透&#xff08;查不到数据&#xff09;&#xff0c;是指当用户想要查询数据的时候&#xff0c;会先去Redis中取命中&#xff0c;如果Redis中没有该数据&#xff0c;那么就会向数据库中去查找数据。如果数据库中也没有&#xff0c;则该次查询结果失…

Spring学习笔记(五)--Spring的AOP模块

一、AOP的底层原理 AOP的底层原理是动态代理&#xff0c;动态代理有两种方式&#xff1a;JDK动态代理和CGLib动态代理&#xff0c;在有接口的实现类时我们通常用JDK的动态代理方式&#xff08;默认情况&#xff09;为类创建代理对象&#xff0c;JDK的动态代理方式可以实现无入…

ORM中常用的字段和参数,正反向概念

django表查询测试环境搭建 首先&#xff0c;在此之前我们先来回顾一下之前学习的orm内容 1. django自带一个小型的sqlite3的小型数据库 但是这个数据库的功能非常有限&#xff0c;并且针对日期类型的数据兼容性很差 2. 切换数据库数据(MySQL) 2.1 在django1.x版本中你需要在_…

PotPlayer+Alist挂载并播放网盘视频

文章目录 说明技术WebDAVPotPlayer 操作步骤一&#xff1a;Alist开启WebDAV代理二&#xff1a;PotPlayer连接Alist 说明 Alist网页端播放视频受限&#xff0c;主要是文件大于20MB&#xff0c;由于官方限制&#xff0c;无法播放需要使用user-agent修改插件&#xff0c;设置百度…

《最新出炉》系列初窥篇-Python+Playwright自动化测试-24-处理单选和多选按钮-上篇

1.简介 在工作和生活中&#xff0c;经常会遇到我们需要进行选择的情况&#xff0c;比如勾选我们选择性别&#xff0c;男女两个性别总是不能同时选中的&#xff0c;再比如我们在选择兴趣爱好时&#xff0c;我们可以选择多个自己感兴趣的话题&#xff0c;比如&#xff1a;篮球、…

sqllabs第46关 order by 注入

简介&#xff1a;&#xff08;order by注入-错误回显-POST注入&#xff09; 请求方法&#xff1a;POST 方法&#xff1a;order by注入错误回显数字型注入 先了解下 order by参数注入&#xff1a; order by 注入是指其后面的参数是可控的&#xff0c; order by 不同于我们在 whe…

gem5学习(23):经典缓存——Classic Caches

目录 一、Interconnects 1、Crossbars 二、Debugging 官网教程&#xff1a;gem5: Classic caches 默认缓存是一个带有MSHR&#xff08;未命中状态保持寄存器&#xff09;和WB&#xff08;写缓冲区&#xff09;的非阻塞缓存&#xff0c;用于读取和写入未命中。缓存还可以启用…

[java基础揉碎]this

引出this: 什么是this: java虚拟机会给每个对象分配 this&#xff0c;代表当前对象。 这里的this就是new出来的这个对象 this的本质: this是个引用在堆中指向它自己: this的细节: 访问成员方法: 访问构造器:

精英ECS Z97-MACHINE V1.0 BIOS MX25L6406E

官网上的两个BIOS我都无法亮机&#xff0c;这是我保存出来的BIOS&#xff0c;不知道是否能使用五代的处理器 官网&#xff1a;Z97-MACHINE&#xff5c;Motherboard&#xff5c;产品&#xff5c;ECS 精英电脑 国外老哥的看法&#xff1a;ECS Z97-MACHINE Closer Look: The BIO…