你知道MySQL锁,到底在锁什么吗?

[复制链接]
发表于 2026-2-7 21:08:13 | 显示全部楼层 |阅读模式
媒介

MySQL锁写过不少,那么这篇文章还是来和各人聊一聊MySQL的锁。
只要学盘算机,「锁」永世是一个绕不外的话题。MySQL锁也是一样。
一句话表明MySQL锁:
MySQL锁是办理资源竞争的一种方案。
短短一句话却包罗了3点值得我们注意的变乱:

  •         对什么资源举行竞争?
  •         竞争的方式(大概讨情况)有哪些?
  •         锁是怎样办理竞争的?
这篇文章开始带你循规蹈矩地明白这几个题目。
1. 资源的竞争方式

MySQL对资源的操纵无非就是读、写两种方式,但是由于事件并发实行的存在,因此对同一资源的并发访问存在3种情势:

  •         读—读:并发事件同时读取雷同资源。由于读操纵不会改变资源本身,因此这种情况下并不存在并发安全性题目。
  •         读—写/写—读:一个事件对资源举行读操纵,另一个事件对资源举行写操纵。
  •         写—写:并发事件同时对同一个资源举行写操纵。
2. 读—写/写—读下的题目

假设一种情况,一个事件先对某个资源举行读操纵,然后另一个事件再对该资源举行写操纵,假如两个事件到此为止,肯定不会导致并发题目。
但是事件这种东西,一样平常情况下就是包罗有许多个子操纵啊。
2.1. 幻读

想象一下啊,假设事件T1和T2并发实行,T1先查找了全部name为「王刚蛋」的用户信息,此时发现拥有这个硬汉名字的用户只有一个。然后T2插入了一个同样叫做「王刚蛋」的用户的信息,而且提交了。



2.2. 不可重复读

再来,同样是T1和T2两个事件,T1通过id = 1查询到了一条数据,然后T2紧接着UPDATE(DELETE也可以)了该条记载,差别的是,T2紧接着通过COMMIT提交了事件。

​此时,T1再次实行雷同的查询操纵,会发现数据发生了厘革,name字段由「王刚蛋」酿成了「蝉沐风」。

假如一个事件读到了另一个已提交事件修改过的(大概是删除的)数据,而导致了前后两次读取的数据不同等的情况,这种事件并发题目叫做不可重复读。
2.3. 脏读

变乱还没竣事,假设T1和T2都要访问user_innodb表中id为1的数据,差别的是T1先读取数据,紧接着T2修改了数据的name字段,必要注意的是,T2并没有提交!

​此时,T1再次实行雷同的查询操纵,会发现数据发生了厘革,name字段由「王刚蛋」酿成了「蝉沐风」。

假如一个事件读到了另一个未提交事件修改过的数据,而导致了前后两次读取的数据不同等的情况,这种事件并发题目叫做脏读。
2.4. 锁与MVCC的关系

总结一下:我们在读—写,写—读的情况下会遇到3种读不同等性的题目,脏读、不可重复读以及幻读。
那写—写呢?很显然,在不做任何步调的情况下,并发会出现更大的题目。那该怎么办呢?
齐备的并发题目都可以通过串行化办理,但是串行化服从太低了!
再优化一下,齐备并发题目都可以通过加锁来办理,这种方案我们称为基于锁的并发控制(Lock Bases Concurrency Control, LBCC)!但是在读多写少的情况下,客户端连读取几条记载都必要列队,服从还是太低了!
因此,MySQL的计划者为事件之间的隔离性提供了差别的级别,使得开辟者可以根据本身的业务场景设置差别的隔离级别,来办理(大概部门办理)读—写/写—读下的读同等性题目,而不是一上来就加锁。
这种机制叫做MVCC,假如你对这个概念不是很相识,我发起你停息一下,读一下我的事件的隔离性与MVCC这篇文章,写得贼好!!(自卖自诩一下)
那有了MVCC是不是在读—写/写—读的情况下就不必要锁了呢?那也不是。
MVCC办理的是读—写/写—读中“比力纯粹的读”遇到的同等性题目,包涵我,这是我本身编的词儿。那什么是不纯粹的?拿存款业务举个例子。
假设陀螺要存一笔钱,体系必要先把陀螺的余额读出来,然后在余额的底子上加上本次存款的金额,末了再写入到数据库中。在将余额读出来之后,假如不想让其他事件继续访问该余额,直到整个存款事件完成之后,其他事件才可以对该余额继续举行操纵,这种情况下就必须为余额的读取操纵添加锁。
再总结一下:MVCC是MySQL默认的办理读—写/写—读下同等性题目的方式,不必要加锁。而锁是实现同等性的终极兜底方案,在某些特别场景下,锁的使用不可克制。
说得更精确一点,MVCC是MySQL在READ COMMITTED、REPEATABLE READ这两种隔离级别之下实行平凡SELECT操纵时默认办理同等性题目的方式。 具体为什么只是这两种隔离级别,发起你看看事件的隔离性与MVCC。
2.5. 锁与事件的关系

事件是多个操纵的聚集,好比我们可以把「把大象装冰箱」这件变乱作为一个事件。

事件有A(原子性)、C(同等性)、I(隔离性)、D(长期性)4大特性,而锁就是实现隔离性的此中一种方案(好比尚有MVCC等方案)。

事件的隔离性针对差别场景需求又实现了差别的隔离级别,差别的隔离级别下,事件使用锁的方式又会有所差别。举个例子。
在READ COMMITTED、REPEATABLE READ这两种隔离级别之下,SELECT操纵是不必要加锁的,直接使用MVCC机制即可满意当前隔离级别的需求。但是在SERIALIZABLE隔离级别,而且在禁用自动提交时(autocommit=0),MySQL会将平凡的SELECT语句转化为SELECT ... LOCK IN SHARE MODE如许的加锁语句,假如你看不懂这句话也没关系,你只必要知道MySQL自动加锁了就行,更具体的下文再说。
别的,一个事件大概会加许多个锁,但是某个锁肯定只属于一个事件。这就好比一个管理员可以管理多个保险柜,一个保险柜肯定只被一个管理员管理。
3. 写—写情况

写—写的情况下肯定要加锁的了,以是接下来终于要聊一聊锁了。
我们起首研究一下锁住的东西的巨细,也就是锁的粒度。
4. 锁的粒度

举一个非常应景的例子。疫情防控的时间,是封锁整个小区还是封锁某栋楼的某个单元,这完满是两种概念。
对应到MySQL锁的粒度,那就是表锁和行锁。
很轻易想到,封锁小区的举动远比封锁某栋楼某单元的举动粗旷,因此,
从锁定粒度上来看,表锁 > 行锁
直接堵住小区的门口要比进入小区找到具体某栋楼的某个单元要快不少,因此,
从加锁服从上来看,表锁 > 行锁
直接锁住小区大概率会影响其他楼住民的正常生存和各种社会活动的开展,而锁住某栋楼某单元顶多影响这一个单元的住民的生存,因此,
从辩论概率来看,表锁 > 行锁
从并发性能来看,表锁 < 行锁
MySQL支持许多存储引擎,而差别的存储引擎对锁的支持也不尽雷同。对于MyISAM、MERGE、MEMORY这些存储引擎而言,只支持表锁;而InnoDB存储引擎既支持表锁也支持行锁,下文讨论的全部内容均针对InnoDB存储引擎。
说完锁的粒度,尚有一件变乱必要我们细致思量一下。上文说过,READ COMMITTED、REPEATABLE READ这两种隔离级别之下,SELECT操纵默认采取MVCC机制就可以了,压根儿不必要加锁,那么题目来了,万一我就是想加锁呢?
你大概会说,“简朴啊,那就加锁!把数据锁死!除了我谁也别动!”
很好,但是对于大部门读—读而言,由于不会出现读同等性题目,以是不让其他事件举行读操纵并不公道。
你大概又说,“那行吧,那就让读操纵加锁的时间允许其他事件对锁住的数据举行读操纵,但是不允许写操纵。”
嗯,想得确实更过细了一些。但是再想想我上文中举过的陀螺存钱的例子,偶尔候SELECT操纵必要独占数据,其他事件既不能读,更不能写。


我们把这种共享和排他的性子称为锁的根本模式。
5. 锁的根本模式

5.1. 共享锁

共享锁(Shared Lock),简称S锁,可以同时被多个事件共享,也就是说,假如一个事件给某个数据资源添加了S锁,其他事件也被允许获取该数据资源的S锁。
由于S锁通常被用于读取数据,因此也被称为读锁。
那怎么给数据添加S锁呢?
我们可以用 SELECT ... LOCK IN SHARE MODE; 的方式,在读取数据之前就为数据添加一把S锁。假如当前事件实行了该语句,那么会为读取到的记载添加S锁,同时其他事件也可以使用SELECT ... LOCK IN SHARE MODE; 方式继续获取这些数据的S锁。
我们通过以下的例子验证一下S锁是否可以重复获取。


5.2. 排他锁

排他锁(Exclusive Lock),简称X锁。只要一个事件获取了某数据资源的X锁,其他的事件就不能再获取该数据的X锁和S锁。
由于X锁通常被用于修改数据,因此也被称为写锁。
X锁的添加方式有两种,

  •         自动添加X锁
  •         我们对记载举行增编削时,通常情况下会自动对其添加X锁。
  •         手动加锁
  •         我们可以用 SELECT ... FOR UPDATE; 的方式,在读取数据之前就为数据添加一把X锁。假如当前事件实行了该语句,那么会为读取到的记载添加X锁,如许既不允许其他事件获取这些记载的S锁,也不允许获取这些记载的X锁。
我们用下面的例子验证一下X锁的排他性。


通常情况下,事件提交或竣事事件时,锁会被开释。
6. 意向锁

6.1. 配景

前面提到的S锁和X锁的语法规则实在是针对记载的,也就是行锁,缘故因由是InnoDB中行锁用的最多。假如将锁的粒度和锁的根本模式分列组合一下,就会出现如下4种情况:

  •         行级S锁
  •         行级X锁
  •         表级S锁
  •         表级X锁
那么接下来的形貌,也就顺理成章了。
假如事件给一个表添加了表级S锁,则:

  •         其他事件可以继续得到该表的S锁,但是无法获取该表的X锁;
  •         其他事件可以继续得到该表某些行的S锁,但是无法获取该表某些行的X锁。
假如事件给一个表添加了表级X锁,则:

  •         岂论是该表的S锁、X锁,还是该表某些行的S锁、X锁,其他事件都只醒目瞪眼儿,啥也获取不了。
挺好明白的吧,总之就是S锁只能和S锁相容,X锁和其他任何锁都互斥。题目来了,固然用的不多,但是万一我真的想给整个表添加一个S锁大概X锁怎么办?
假如我要给表user添加一个S锁,那就必须包管user在表级别上和行级别上都不能有X锁,表级别上还好说一点,无非就是1个内存结构罢了,但是行X锁呢?必须得逐行遍历是否有行X锁吗?
同理,假如我要给表user添加一个X锁,那就必须包管user在表级别上和行级别上都不能有任何锁(S和X都不能有),难不成得逐行遍历是否有S或X锁吗?
遍历是不大概遍历的!这辈子都不大概遍历的!于是,意向锁(Intension Lock)诞生了。
6.2. 概念

我们要克制遍历,那最好的办法就是在给行加锁时,先在表级别上添加一个标识。

  •         意向共享锁(Intension Shared Lock):简称IS锁,当事件试图给行添加S锁时,必要先在表级别上添加一个IS锁;
  •         意向排他锁(Intension Exclusive Lock):简称IX锁,当事件试图给行添加X锁时,必要先在表级别上添加一个IX锁。
如许一来:

  •         假如想给user表添加一个S锁(表级锁),就先看一下user表有没有IX锁;假如有,就阐明user表的某些行被加了X锁(行锁),必要比及行的X锁开释,随即IX锁被开释,才可以在user表中添加S锁;
  •         假如想给user表添加一个X锁(表级锁),就先看一下user有没有IS锁或IX锁;假如有,就阐明user表的某些行被加了S锁或X锁(行锁),必要比及全部行锁被开释,随即IS锁或IX锁被开释,才可以在user表中添加X锁。
必要注意的是,意向锁和意向锁之间是不辩论的,意向锁和行锁之间也不辩论。 只有在对表添加S锁或X锁时才必要判定当前表是否被添加了IS锁或IX锁,当为表添加IS锁或IX锁时,不必要关心当前表是否已经被添加了其他IS锁或IX锁。
现在为止MySQL锁的根本模式就先容完了,接下来回到这片文章的标题,MySQL锁,锁住的到底是什么?由于InnoDB的行锁用的最多,这里的锁自然指的是行锁。
7. 行锁的原理

既然都叫行锁了,我们姑且推测一下,行锁锁住的是一行数据。我们做个实行。
7.1. 没有任何索引的表

我们先创建一张没有任何索引的平凡表,语句如下
  1. CREATE TABLE `user_t1` ( `id` int DEFAULT NULL, `name` varchar(255) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
复制代码
表中数据如下:
  1. mysql> SELECT * FROM user_t1; +------+-------------+ | id | name | +------+-------------+ | 1 | chanmufeng | | 2 | wanggangdan | | 3 | wangshangju | | 4 | zhaotiechui | +------+-------------+
复制代码
接下来我们在两个session中开启两个事件。

  •         事件1,我们通过WHERE id = 1“锁住”第1行数据;
  •         事件2,我们通过WHERE id = 2"锁住"第2行数据。

​一件诡异的变乱是,第2个加锁的操纵被壅闭了。现实上,T2中不管我们要给user_t1中哪行数据加锁,都会失败!

为什么我SELECT一条数据,却给我锁住了整个表?这个实行直接颠覆了我们的推测,InnoDB的行锁并非直接锁定Record行。
为什么没有索引的情况下,给某条语句加锁会锁住整个表呢?别急,我们继续。
7.2. 有主键索引的表

我们再创建一个表user_t2,语句如下:
  1. CREATE TABLE `user_t2` ( `id` int NOT NULL, `name` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
复制代码
和user_t1的差别之处在于为id创建了一个主键索引。表中数据依然如下:
  1. mysql> SELECT * FROM user_t2; +------+-------------+ | id | name | +------+-------------+ | 1 | chanmufeng | | 2 | wanggangdan | | 3 | wangshangju | | 4 | zhaotiechui | +------+-------------+
复制代码
同样开启两个事件:

  •         事件1,通过WHERE id = 1“锁住”第1行数据;
  •         事件2依然使用WHERE id = 1实行加锁,加锁失败;使用WHERE id = 2实行加锁,加锁乐成。

​既然锁的不是Record行,难不成锁的是id这一列吗?

我们再做末了一个实行。
7.3. 有唯一索引的表

我们再创建一个表user_t3,语句如下:
  1. CREATE TABLE `user_t3` ( `id` int NOT NULL, `name` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY (`uk_name`) (`name`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4;
复制代码
和user_t2的差别之处在于为name列创建了一个唯一索引。表中数据依然如下:
  1. mysql> SELECT * FROM user_t3; +------+-------------+ | id | name | +------+-------------+ | 1 | chanmufeng | | 2 | wanggangdan | | 3 | wangshangju | | 4 | zhaotiechui | +------+-------------+
复制代码

​两个事件:


  •         事件1,通过name字段 “锁住”name为“chanmufeng”的数据;
  •         事件2依然使用WHERE name = “chanmufeng” 实行加锁,可以预料,加锁失败;使用WHERE id = 1实行给同样的行加锁,加锁失败。
通过3个实行我们发现,行锁锁住的既不是Record行,也不是Column列,那到底锁住的是什么?我们对比一下,上文的3张表的差别点在于索引差别,实在InnoDB的行锁,就是通过锁住索引来实现的。
索引是个啥?再给你保举一下我之前写的文章,

  •         图解|12张图表明MySQL主键查询为什么这么快
  •         图解|这次,彻底明白MySQL的索引
接下来答复3个题目。
8. 三个题目

8.1. 锁住索引?没有索引怎么办?

你说锁住索引?假如我不创建索引,MySQL锁定个啥?
假如我们没有设置主键,InnoDB会优先选取一个不包罗NULL值的Unique键作为主键,假如表中连Unique键也没有的话,就会自动为每一条记载添加一个叫做DB_ROW_ID的列作为默认主键,只不外这个主键我们看不到罢了。
下图是数据的行格式。看不懂的话剧烈保举看一下我上面给出的两篇文章,说得非常明确。



8.2. 为什么第一个实行会锁表?

由于SELECT没有用到索引,会举行全表扫描,然后把DB_ROW_ID作为默认主键的聚簇索引都给锁住了。
8.3. 为什么通过唯一索引给数据加锁,主键索引也会被锁住?

不管是Unique索引还是平凡索引,它们的叶子结点中存储的数据都不完备,此中只是存储了作为索引而且排序好的列数据以及对应的主键值。
因此我们通过索引查找数据数据现实上是在索引的B+树中先找到对应的主键,然后根据主键再去主键索引的B+树的叶子结点中找到完备数据,末了返回。以是固然是两个索引树,但现实上是同一行数据,必须全部锁住。
下面给了一张图,让不相识索引的朋侪大抵相识一下。上半部门是name列创建的唯一索引的B+树,下半部门是主键索引(也叫聚簇索引)。
假如我们通过WHERE name = '王钢蛋'对数据举行查询,会先用到name列的唯一索引,终极定位到主键值为1,然后再到主键索引中查询id = 1的数据,终极拿到完备的行数据。



9. 总结

至此,我已经答复了文章开头的绝大多数题目。
MySQL锁,是办理资源竞争题目的一种本领。有哪些竞争呢?读—写/写—读,写—写中都会出现资源竞争题目,差别的是前者可以通过MVCC的方式来办理,但是某些情况下你也不得不消锁,因此我也趁便表明白锁和MVCC的关系。
然后先容了MySQL锁的根本模式,包罗共享锁(S锁)和排他锁(X锁),还引入了意向锁。
末了表明白锁到底锁的是什么的题目。通过3个实行,终极表明白InnoDB锁本质上锁的是索引。
本文并没有先容MySQL中具体的锁算法,也就是怎样办理资源竞争的,好比Record Locks、Gap Locks、Next-Key Locks等,更细节的内容下以后偶尔间再见喽~

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!qidao123.com:ToB企服之家,中国第一个企服评测及软件市场,开放入驻,技术点评得现金

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表