MySQL 中的锁(一)

news2025/1/20 7:23:52

MySQL 中的锁

按照 MySQL 官方的说法,InnoDB 中锁可以分为:
在这里插入图片描述
可见,InnoDB 中锁非常多,总的来说,可以如下分类:
在这里插入图片描述
这些锁都是做什么的?具体含义是什么?我们现在来一一学习。

8.1. 解决并发事务问题

我们已经知道事务并发执行时可能带来的各种问题,最大的一个难点是:一
方面要最大程度地利用数据库的并发访问,另外一方面还要确保每个用户能以一
致的方式读取和修改数据,尤其是一个事务进行读取操作,另一个同时进行改动
操作的情况下。

8.1.1. 复习并发事务问题

一个事务进行读取操作,另一个进行改动操作,我们前边说过,这种情况下
可能发生脏读、不可重复读、幻读的问题。
SQL 标准规定不同隔离级别下可能发生的问题不一样:
在 READ UNCOMMITTED 隔离级别下,脏读、不可重复读、幻读都可能发生。
在 READ COMMITTED 隔离级别下,不可重复读、幻读可能发生,脏读不可以发生。
在 REPEATABLE READ 隔离级别下,幻读可能发生,脏读和不可重复读不可以发生。
在 SERIALIZABLE 隔离级别下,上述问题都不可以发生。
不过各个数据库厂商对 SQL 标准的支持都可能不一样,与 SQL 标准不同的一
点就是,MySQL 在 REPEATABLE READ 隔离级别实际上就基本解决了幻读问题。
怎么解决脏读、不可重复读、幻读这些问题呢?其实有两种可选的解决方案:

8.1.2. 方案一:读操作 MVCC,写操作进行加锁

所谓的 MVCC 我们在前一章有过详细的描述,就是通过生成一个 ReadView,
然后通过 ReadView 找到符合条件的记录版本(历史版本是由 undo 日志构建的),
其实就像是在生成 ReadView 的那个时刻做了一个快照,查询语句只能读到在生
成 ReadView 之前已提交事务所做的更改,在生成 ReadView 之前未提交的事务或
者之后才开启的事务所做的更改是看不到的。而写操作肯定针对的是最新版本的
记录,读记录的历史版本和改动记录的最新版本本身并不冲突,也就是采用
MVCC 时,读-写操作并不冲突。

我们说过普通的 SELECT 语句在 READ COMMITTED 和 REPEATABLE READ 隔离
级别下会使用到 MVCC 读取记录。在 READ COMMITTED 隔离级别下,一个事务
在执行过程中每次执行 SELECT 操作时都会生成一个 ReadView,ReadView 的存在
本身就保证了事务不可以读取到未提交的事务所做的更改,也就是避免了脏读现
象;REPEATABLE READ 隔离级别下,一个事务在执行过程中只有第一次执行
SELECT 操作才会生成一个 ReadView,之后的 SELECT 操作都复用这个 ReadView,
这样也就避免了不可重复读和很大程度上避免了幻读的问题。

8.1.3. 一致性读(Consistent Reads)/快照读

事务利用 MVCC 进行的读取操作称之为一致性读,或者一致性无锁读,也称
之为快照读,但是往往读取的是历史版本数据。所有普通的 SELECT 语句(plain
SELECT)在 READ COMMITTED、REPEATABLE READ 隔离级别下都算是一致性读。
上面的这句话中,普通的 SELECT 语句是指不加锁的 select 语句在非串行化事务隔离级别下。

一致性读并不会对表中的任何记录做加锁操作,其他事务可以自由的对表中的记录做改动。

很明显,采用 MVCC 方式的话,读-写操作彼此并不冲突,性能更高,采用
加锁方式的话,读-写操作彼此需要排队执行,影响性能。一般情况下我们当然
愿意采用 MVCC 来解决读-写操作并发执行的问题,但是业务在某些情况下,要
求必须采用加锁的方式执行。

8.1.4. 方案二:读、写操作都采用加锁的方式

如果我们的一些业务场景不允许读取记录的旧版本,而是每次都必须去读取
记录的最新版本,比方在银行存款的事务中,你需要先把账户的余额读出来,然
后将其加上本次存款的数额,最后再写到数据库中。在将账户余额读取出来后,
就不想让别的事务再访问该余额,直到本次存款事务执行完成,其他事务才可以
访问账户的余额。这样在读取记录的时候也就需要对其进行加锁操作,这样也就
意味着读操作和写操作也像写-写操作那样排队执行。

我们说脏读的产生是因为当前事务读取了另一个未提交事务写的一条记录,
如果另一个事务在写记录的时候就给这条记录加锁,那么当前事务就无法继续读取该记录了,所以也就不会有脏读问题的产生了。

不可重复读的产生是因为当前事务先读取一条记录,另外一个事务对该记录做了改动之后并提交之后,当前事务再次读取时会获得不同的值,如果在当前事务读取记录时就给该记录加锁,那么另一个事务就无法修改该记录,自然也不会发生不可重复读了。

我们说幻读问题的产生是因为当前事务读取了一个范围的记录,然后另外的事务向该范围内插入了新记录,当前事务再次读取该范围的记录时发现了新插入的新记录,我们把新插入的那些记录称之为幻影记录。采用加锁的方式解决幻读问题就有不太容易了,因为当前事务在第一次读取记录时那些幻影记录并不存在,所以读取的时候加锁就有点麻烦 —— 因为并不知道给谁加锁。InnoDB 中是如何解决的,我们后面会讲到。

8.2. 锁定读(Locking Reads)/LBCC

也称当前读, 读取的是最新版本, 并且对读取的记录加锁, 阻塞其他事务同时改动相同记录,避免出现安全问题。
哪些是当前读呢?select lock in share mode (共享锁)、select for update (排他
锁)、update (排他锁)、insert (排他锁)、delete (排他锁)、串行化事务隔离级别都是当前读。

当前读这种实现方式,也可以称之为 LBCC(基于锁的并发控制,Lock-Based
Concurrency Control),怎么做到?

8.2.1.1. 共享锁和独占锁

在使用加锁的方式解决问题时,由于既要允许读-读情况不受影响,又要使
写-写、读-写或写-读情况中的操作相互阻塞,MySQL 中的锁有好几类:

  • 共享锁,英文名:Shared Locks,简称 S 锁。在事务要读取一条记录时,需要先获取该记录的 S 锁。

  • 独占锁,也常称排他锁,英文名:Exclusive Locks,简称 X 锁。在事务要改动一条记录时,需要先获取该记录的 X 锁。

  • 假如事务 E1 首先获取了一条记录的 S 锁之后,事务 E2 接着也要访问这条记录:

  • 如果事务 E2 想要再获取一个记录的 S 锁,那么事务 E2 也会获得该锁,也就意味着事务 E1 和 E2 在该记录上同时持有 S 锁。

  • 如果事务 E2 想要再获取一个记录的 X 锁,那么此操作会被阻塞,直到事务E1 提交之后将 S 锁释放掉。

  • 如果事务 E1 首先获取了一条记录的 X 锁之后,那么不管事务 E2 接着想获取该记录的 S 锁还是 X 锁都会被阻塞,直到事务 E1 提交。

  • 所以我们说 S 锁和 S 锁是兼容的,S 锁和 X 锁是不兼容的,X 锁和 X 锁也是不兼容的,画个表表示一下就是这样:

X 不兼容 X 不兼容 S
S 不兼容 X 兼容 S

8.2.1.2. 锁定读的 SELECT 语句

MySQ 有两种比较特殊的 SELECT 语句格式:
对读取的记录加 S 锁:
SELECT … LOCK IN SHARE MODE;

也就是在普通的 SELECT 语句后边加 LOCK IN SHARE MODE,如果当前事务执
行了该语句,那么它会为读取到的记录加 S 锁,这样允许别的事务继续获取这些
记录的 S 锁(比方说别的事务也使用 SELECT … LOCK IN SHARE MODE 语句来读取
这些记录),但是不能获取这些记录的 X 锁(比方说使用 SELECT … FOR UPDATE
语句来读取这些记录,或者直接修改这些记录)。

如果别的事务想要获取这些记录的 X 锁,那么它们会阻塞,直到当前事务提交之后将这些记录上的 S 锁释放掉。
对读取的记录加 X 锁:
SELECT … FOR UPDATE;
也就是在普通的 SELECT 语句后边加 FOR UPDATE,如果当前事务执行了该语
句,那么它会为读取到的记录加 X 锁,这样既不允许别的事务获取这些记录的 S
锁(比方说别的事务使用 SELECT … LOCK IN SHARE MODE 语句来读取这些记录),
也不允许获取这些记录的 X 锁(比如说使用 SELECT … FOR UPDATE 语句来读取这
些记录,或者直接修改这些记录)。
如果别的事务想要获取这些记录的 S 锁或者 X 锁,那么它们会阻塞,直到当前事务提交之后将这些记录上的 X 锁释放掉。

8.2.1.3. 写操作的锁

平常所用到的写操作无非是 DELETE、UPDATE、INSERT 这三种:

DELETE:
对一条记录做 DELETE 操作的过程其实是先在 B+树中定位到这条记录的位置,
然后获取一下这条记录的 X 锁,然后再执行 delete mark 操作。我们也可以把这
个定位待删除记录在 B+树中位置的过程看成是一个获取 X 锁的锁定读。
INSERT:
一般情况下,新插入一条记录的操作并不加锁,InnoDB 通过一种称之为隐
式锁来保护这条新插入的记录在本事务提交前不被别的事务访问。当然,在一些
特殊情况下 INSERT 操作也是会获取锁的,具体情况我们后边再说。

UPDATE:
在对一条记录做 UPDATE 操作时分为三种情况:
如果未修改该记录的键值并且被更新的列占用的存储空间在修改前后未发
生变化,则先在 B+树中定位到这条记录的位置,然后再获取一下记录的 X 锁,
最后在原记录的位置进行修改操作。其实我们也可以把这个定位待修改记录在
B+树中位置的过程看成是一个获取 X 锁的锁定读。

如果未修改该记录的键值并且至少有一个被更新的列占用的存储空间在修改前后发生变化,则先在 B+树中定位到这条记录的位置,然后获取一下记录的 X锁,将该记录彻底删除掉(就是把记录彻底移入垃圾链表),最后再插入一条新记录。这个定位待修改记录在 B+树中位置的过程看成是一个获取 X 锁的锁定读,新插入的记录由 INSERT 操作提供的隐式锁进行保护。

如果修改了该记录的键值,则相当于在原记录上做 DELETE 操作之后再来一次 INSERT 操作,加锁操作就需要按照 DELETE 和 INSERT 的规则进行了。

8.3. 锁的粒度

我们前边提到的锁都是针对记录的,也可以被称之为行级锁或者行锁,对一条记录加锁影响的也只是这条记录而已,我们就说这个锁的粒度比较细;其实一个事务也可以在表级别进行加锁,自然就被称之为表级锁或者表锁,对一个表加锁影响整个表中的记录,我们就说这个锁的粒度比较粗。给表加的锁也可以分为共享锁(S 锁)和独占锁(X 锁)

8.3.1.1. 表锁与行锁的比较

锁定粒度:表锁 > 行锁
加锁效率:表锁 > 行锁
冲突概率:表锁 > 行锁
并发性能:表锁 < 行锁

8.3.1.2. 给表加 S 锁

如果一个事务给表加了 S 锁,那么:
别的事务可以继续获得该表的 S 锁
别的事务可以继续获得该表中的某些记录的 S 锁
别的事务不可以继续获得该表的 X 锁
别的事务不可以继续获得该表中的某些记录的 X 锁

8.3.1.3. 给表加 X 锁

如果一个事务给表加了 X 锁(意味着该事务要独占这个表),那么:
别的事务不可以继续获得该表的 S 锁
别的事务不可以继续获得该表中的某些记录的 S 锁
别的事务不可以继续获得该表的 X 锁
别的事务不可以继续获得该表中的某些记录的 X 锁。
为了更好的理解这个表级别的 S 锁和 X 锁和后面的意向锁,我们举一个现实
生活中的例子。我们用曾经很火爆的互联网风口项目共享 Office 来说明加锁:

共享 Office 有栋大楼,楼自然有很多层。办公室都是共享的,客户可以随便选办公室办公。每层楼可以容纳客户同时办公,每当一个客户进去办公,就相当于在每层的入口处挂了一把 S 锁,如果很多客户进去办公,相当于每层的入口处挂了很多把 S 锁(类似行级别的 S 锁)。

有的时候楼层会进行检修,比方说换地板,换天花板,检查水电啥的,这些维修项目并不能同时开展。如果楼层针对某个项目进行检修,就不允许客户来办公,也不允许其他维修项目进行,此时相当于楼层门口会挂一把 X 锁(类似行级别的 X 锁)。

上边提到的这两种锁都是针对楼层而言的,不过有时候我们会有一些特殊的需求:

A、有投资人要来考察 Office 的环境。
投资人和公司并不想影响客户进去办公,但是此时不能有楼层进行检修,所以可以在大楼门口放置一把 S 锁(类似表级别的 S 锁)。此时:来办公的客户们看到大楼门口有 S 锁,可以继续进入大楼办公。
修理工看到大楼门口有 S 锁,则先在大楼门口等着,啥时候投资人走了,把大楼的 S 锁撤掉再进入大楼维修。

B、公司要和房东谈条件。
此时不允许大楼中有正在办公的楼层,也不允许对楼层进行维修。所以可以在大楼门口放置一把 X 锁(类似表级别的 X 锁)。此时:来办公的客户们看到大楼门口有 X 锁,则需要在大楼门口等着,啥时候条件谈好,把大楼的 X 锁撤掉再进入大楼办公。
修理工看到大楼门口有 X 锁,则先在大楼门口等着,啥时候考试结束,把大楼的 X 锁撤掉再进入大楼维修。

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

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

相关文章

Nginx性能调优策略

Nginx是一个高性能的Web服务器和反向代理服务器&#xff0c;常用于处理高并发的请求。以下是一些常见的Nginx性能调优策略&#xff1a; 一、调整worker_processes和worker_connections 在Nginx配置文件中&#xff0c;可以通过worker_processes和worker_connections参数来调整w…

CLIPTokenizer.from_pretrained本地加载

以"openai/clip-vit-large-patch14"为例&#xff0c;原代码为&#xff1a; self.tokenizer CLIPTokenizer.from_pretrained(“openai/clip-vit-large-patch14”) self.transformer CLIPTextModel.from_pretrained(“openai/clip-vit-large-patch14”) 但我连不到外…

ArkTS-取消标题与自定义标题栏

文章目录 取消标头自定义标题栏导入Resources自定义跳转动画关于底部tabBar导航文本输入(TextInput/TextArea)自定义样式添加事件可以是onChange可以是onSubmit List列表组件设置主轴方向 网格布局服务卡片-获取地理位置页面获取地理位置服务卡片获取地理位置 可以先看看&#…

移民同步进行|企业高管自费赴美国奥本大学访学

K经理申请了美国杰出人才移民&#xff0c;已经获批I-140&#xff0c;正在排期中&#xff0c;尚未获得绿卡。为了使孩子同步美国学制&#xff0c;K经理希望先以访问学者身份带孩子出国接受免费公立教育。最终我们落实了奥本大学的职位&#xff0c;申请人及孩子顺利获签出国&…

VSCODE+QEMU+WSL调试RISCV代码(SBI、kernel)

前言 最近在对RISC-V架构比较感兴趣&#xff0c;正好手头有《RISC-V体系结构编程与实践》的书籍&#xff0c;就打算跟随笨叔将这块的知识学习起来&#xff0c;最开始当然是需要搭建一个基础的实验平台&#xff0c;本来笨叔是贴心的提供了VMare的环境&#xff0c;奈何天生叛逆的…

Matlab下载许可证文件 教程(在账号有许可证的前提下)

文章目录 Part.I IntroductionPart.II 许可证文件过期解决方案Chap.I 使用 Internet 自动激活Chap.II 在不使用 Internet 的情况下手动激活 Part.I Introduction 本文主要介绍&#xff0c;在 Mathwork 账号有许可证的前提下&#xff0c;下载许可证的操作流程。 好久没有用 Mat…

OSCP系列靶场-Esay-1

总结 getwebshell : ftp可匿名登录 → 发现隐藏文件夹 → 发现ssh密钥 → 猜解ssh用户名 → ssh密钥登录 提 权 思 路 : 发现suid权限文件 → cpulimit提权 准备工作 启动VPN 获取攻击机IP → 192.168.45.191 启动靶机 获取目标机器IP → 192.168.179.130 信息收集-端口扫…

Android自定义瀑布流文字展示

在历史搜索功能中&#xff0c;我们常用到一个瀑布流展示控件&#xff0c;用来展示我们的搜索记录&#xff0c;所以就自定义一个吧&#xff01; 布局中代码示例 <com.example.mymodularization.measure.LinearCustomandroid:id"id/ll"android:layout_width"wr…

基于深度学习的点云三维目标检测方法综述

论文标题&#xff1a;基于深度学习的点云三维目标检测方法综述 作者&#xff1a;郭毅锋&#xff11;&#xff0c;&#xff12;†&#xff0c;吴帝浩&#xff11;&#xff0c;魏青民&#xff11; 发表日期&#xff1a; 2023 1 阅读日期 &#xff1a;2023 11 29 研究背景&…

一、Gradle 手动创建一个项目

文章目录 Gradle 介绍Gradle Wrapper Gradle 使用手动安装 Gradle初始化 Gradle 介绍 Gradle 是一个快速的、可信的、适应性强的自动化构建工具&#xff0c;它是开源的。它使用优雅的并且可扩展的描述性语言。其他的介绍在官网可以了解。 Gradle Wrapper 官方建议使用 Gradl…

vue3实现元素拖拽移动功能

效果图 实现拖拽移动 首先我们给需要实现功能的元素加一个draggable"true"让元素能够被拖拽 先来认识两个搭配draggable属性一起使用的事件——ondragstart和ondragend&#xff0c;它们的定义分别为&#xff1a; ①. ondragstart 事件在用户开始拖动元素或选择的文…

Python中使用matplotlib库绘图中如何给图形的图例设置中文字体显示

问题&#xff1a;当使用matplotlib绘图时遇到绘图&#xff0c;图例显示不出来中文字体 解决方式&#xff1a; 1&#xff09;加载字体管理库 from matplotlib.font_manager import FontProperties 2&#xff09;设置系统上字体的路径 font FontProperties(fname"C:\\W…

docker搭建node环境开发服务器

docker搭建node环境开发服务器 本文章是我自己搭建node环境开发服务器的过程记录&#xff0c;不一定完全适用所有人。根据个人情况&#xff0c;按需取用。 命名项目路径 为了方便cd到项目路径&#xff0c;将项目路径重命名&#xff0c;方便输入。 vim /etc/profile # 修改p…

Linux 下命令行启动与关闭WebLogic的相关服务

WebLogic 的服务器类型 WebLogic提供了三种类型的服务器&#xff1a; 管理服务器节点服务器托管服务器 示例和关系如下图&#xff1a; 对应三类服务器&#xff0c; 就有三种启动和关闭的方式。本篇介绍使用命令行脚本的方式启动和关闭这三种类型的服务器。 关于WebLogic 的…

如何通过“闻香”给葡萄酒分类?

有句话叫做“闻香识女人”&#xff0c;葡萄酒也如同美女&#xff0c;千娇百媚风情万种&#xff0c;所以通过“闻香”也可以给葡萄酒进行分类。 那么&#xff0c;云仓酒庄的品牌雷盛红酒分享葡萄酒都有哪些不同的香呢&#xff1f; 云仓酒庄是云仓酒庄的结合&#xff0c;也就是在…

深入了解Java8新特性-日期时间API之ZonedDateTime类

阅读建议 嗨&#xff0c;伙计&#xff01;刷到这篇文章咱们就是有缘人&#xff0c;在阅读这篇文章前我有一些建议&#xff1a; 本篇文章大概19000多字&#xff0c;预计阅读时间长需要10分钟以上。本篇文章的实战性、理论性较强&#xff0c;是一篇质量分数较高的技术干货文章&…

群晖安装portainer

一、下载镜像 打开【Container Manager】 ,搜索portainer&#xff0c;双击【6053537/portainer-ce】下载汉化版本 二、创建映射文件夹 打开【File Station】&#xff0c;在docker目录下创建【portainer】文件夹 三、开启SSH 群晖 - 【控制面板】-【终端机和SNMP】 勾选【启动…

oracle数据库节点一宕机重启后集群crsd服务没有起

13:18:55时节点一服务器宕机后&#xff0c;节点2心跳不通剔除了节点1 之后节点1服务器重启 集群设置自动拉起&#xff0c;但节点一启动后集群在crsd服务上迟迟没有起来 去查看了crsd的日志发现这一时间点心跳一直不通 在节点一起crsd服务&#xff0c;执行以下命令 #&#x…

Cytoscape学习教程

写在前面 今天分享的内容是自己遇到问题后,咨询社群里面的同学,帮忙解决的总结。 关于Cytoscape,对于做组学或生物信息学的同学基本是陌生的,可能有的同学用这个软件作图是非常溜的,做出来的网络图也是十分的好看,“可玩性”很高,就像前面分享的aPEAR包一样aPEAR包绘制…

0基础能不能转行做网络安全?网络安全人才发展路线

最近有同学在后台留言&#xff0c;0基础怎么学网络安全&#xff1f;0基础可以转行做网络安全吗&#xff1f;以前也碰到过类似的问题&#xff0c;想了想&#xff0c;今天简单写一下。 我的回答是先了解&#xff0c;再入行。 具体怎么做呢&#xff1f; 首先&#xff0c;你要确…