文章目录
- 认识事务的隔离性
- 具体认识四种隔离级别
- 1.读未提交【Read Uncommitted】
- 2.读提交【Read Committed】
- 3.可重复读 【Repeatable Read】
- 4.串行化【serializable】
- 不同的隔离级别的安全问题
- 事务的一致性
认识事务的隔离性
认识事务的隔离级别
如何理解事务的原子性?
一个事务中可以有多条sql语句,也就是说一条事务可能会对数据库做很多操作。而事务的原子性指的就是,一条事务内的多条sql语句是同生共死的,他们要么一起执行,要么都不会被执行,不会出现事务结束之后一些语句执行成功,另外一些执行失败的情况。如果这个事务的某条操作执行过程中遇到了点问题,没法顺利完成,那其余的操作也不会执行,mysql会把数据库恢复到事务开始之前的状态
一个事务可能由多条SQL构成,也就意味着,任何一个事务,都有执行前,执行中,执行后的阶段。而所谓的原子性,其实就是让用户层,要么看到执行前,要么看到执行后。事物执行中出现问题,执行不下去了,就立即回滚到执行前。
事务的隔离级别有哪些?
- 读未提交(Read Uncommitted)
- 读已提交(Read Committed)
- 可重复读(Repeatable Read)
- 串行化(Serializable)
为什么要划分四种隔离级别?
隔离级别存在的意义是什么?为什么要划分出来这些隔离级别?如果是要解决多请求并发问题,那我给线程访问临界资源的动作上锁不就行了吗?
引入隔离级别最大的原因在于,mysql不仅想解决多请求并发处理问题,实现多线程对数据库的安全访问,还想在保证安全性的基础上,最大程度地提高执行效率(mysql想既要还要)。
四种隔离级别对安全性和执行效率做的平衡是不一样的。比如串行化,它是最安全的,但是执行效率是最慢的,读未提交(Read Uncommitted)是最不安全的,但是执行效率是最快的。如果单单是为了解决多请求并发处理的安全问题,那确实简单,直接上锁(对应串行化)就行了。但是在具体的场景中,可能有些场景不容易出现安全性的问题,不需要你给他提供那么严格的安全性保障,那我们就可以更弱一些的隔离级别,牺牲一部分安全性,从而为mysql运行换来更高的性能。
前面说的加锁,确实可以解决并发带来的数据不一致问题,但也是有缺点的,缺点是,你这种方式就让多线程访问临界资源时完全丧失了并发性。转化成了多线程串行执行,会导致各个线程的执行效率大大降低,这会直接影响到我们对数据库增删查改操作的执行效率
mysql对上述效率是很看重的,他想在确保安全性的基础上,尽可能提高进程执行的效率,于是就给隔离的程度划分了等级
像你刚刚说的那种方案,就是隔离等级的第四级,也就是最高级别的隔离,除此之外还有较低级别的三种隔离等级,等级越低的隔离越不安全,但效率越高。相当于用安全性去换效率
如何查看隔离级别?
隔离级别在宏观上又分为全局事务隔离级别和会话事务隔离级别,这俩含义也是不一样的
MSYQL中的会话是什么
一个MYSQL的会话 对应 一个客户端与MYSQL服务器之间建立起来的一个链接。连接是会话的 “物理载体”,一个连接对应一个会话,无论发起连接的客户端是否相同(比如是同一台电脑的 Navicat、同一程序的不同线程),只要是两次独立的连接请求(即建立了两条独立的 TCP 通信链路),MySQL 服务器就会为每一次连接分配独立的 “会话资源”
全局事务隔离级别和会话事务隔离级别有啥区别?
全局的可以管所有会话,当你修改了全局事务隔离级别之后,后面创建的新会话事务,其默认隔离级别就是你新改的全局事务隔离级别
会话事务隔离级别就管着这一个事务,当你修改了会话事务隔离级别之后,当前这个事务的隔离级别会变成你修改的那个值,但是后续新创建的事务隔离级别仍然由全局的隔离级别决定
如何查看当前全局事务的隔离级别?
select @@global.tx_isolation;
其中@@ 是用于引用系统变量的符号。也就是说tx_isolation是一个系统变量
如何查看当前会话事务的隔离级别?
select @@session.tx_isolation;
或者我们也可以直接输入select @@tx_isolation;它表示的也是当前会话事务的隔离级别
如何更改/设置隔离级别
如何更改/设置全局事务隔离级别?设置全局事务隔离级别有啥作用?
SET GLOBAL TRANSACTION ISOLATION LEVEL + 具体的隔离级别;
其中具体的隔离级别是我们前面说过的那四种中的一种
- READ - UNCOMMITTED读未提交
- READ - COMMITTED(读已提交)
- REPEATABLE - READ(可重复读)
- SERIALIZABLE(串行化)
比如SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
执行这条语句之后,后面MYSQL创建出的新会话的隔离级别默认都是READ COMMITTED(执行该语句的当前会话的隔离级别不受影响)
如何设置当前会话的事务隔离级别?
SET SESSION TRANSACTION ISOLATION LEVEL + 具体的隔离级别;
比如 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
这条语句执行之后,当前会话后面创建的新事务,隔离级别都是 READ COMMITTED。
但是如果当前会话结束了,假如说当前全局隔离级别设置的是REPEATABLE - READ(可重复读),那下次我创建一个新会话,该会话的默认事务隔离级别就不是 READ COMMITTED了,而是REPEATABLE - READ(可重复读)
一个已经开始的事务,其隔离级别还能改吗?
在 MySQL 中,一个已经开始的事务(活跃事务),其隔离级别无法被修改。一旦事务通过 START TRANSACTION(或隐式开启,如 autocommit=0 时的第一条 SQL)启动,它的隔离级别就被固定了,后续即使执行 SET TRANSACTION 或 SET SESSION TRANSACTION 语句,也不会影响这个正在进行的事务。
隔离级别修改指令的生效范围
输入上面更改隔离级别的指令之后,隔离级别会立即生效吗?当前还没提交的事务受不受影响?
执行SET GLOBAL TRANSACTION ISOLATION LEVEL + 具体的隔离级别;后,全局隔离级别会立即更新,但并不会对当前已有的会话产生影响。
执行SET SESSION TRANSACTION ISOLATION LEVEL + 具体的隔离级别;后,当前会话的事务隔离级别会立即更新,但并不会对当前会话正在进行中的事务产生影响
假如说当前会话隔离级别设置的是REPEATABLE - READ(可重复读),然后我在当前会话中执行SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;,将全局隔离级别设置READ COMMITTED,那请问当前会话创建的下一条事务,他的隔离级别是什么呢?
是当前会话的隔离级别REPEATABLE - READ(可重复读)。因为全局隔离级别的设置语句并不会改变当前会话的隔离级别
假如说当前会话隔离级别设置的是REPEATABLE - READ(可重复读),然后我在当前会话中执行SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;,将当前会话的隔离级别修改为READ COMMITTED,那请问当前会话创建的下一条事务,他的隔离级别是什么呢?
是当前会话修改之后的隔离级别READ COMMITTED
假如说当前会话隔离级别设置的是REPEATABLE - READ(可重复读),在当前会话中我创建了一个事务还没提交,这时候我在当前会话中执行SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;,将当前会话的隔离级别修改为READ COMMITTED,那么请问还没提交的那个事务,它的隔离级别是什么呢?
是REPEATABLE - READ(可重复读),该事务的隔离级别 以 当前会话创建该事务时的隔离级别 为准
登出再登录对隔离级别的影响
当你登出当前会话并重新登录后,服务器就会为当前的新链接创建一个新的会话。新的会话默认采用最新设置的全局隔离级别
假如说当前会话隔离级别设置的是REPEATABLE - READ(可重复读),然后我在当前会话中执行SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;,将全局隔离级别设置READ COMMITTED,然后我直接退出当前会话重新登录。进入新会话时,这个新会话的事务隔离级别就是READ COMMITTED了
具体认识四种隔离级别
1.读未提交【Read Uncommitted】
如何理解读未提交?
意思就是在这种隔离级别下,mysql允许其他事务查看一个事务未提交时的数据。这种隔离级别没有任何隔离性,效率最高。
既然他效率最高,那他安全性肯定是最低的。我现在就想问
读未提交的隔离性下存在哪些安全问题呢?
- 脏读问题
- 不可重复读问题
- 幻读问题
什么是脏读问题 ?
幻读和不可重复读问题我们后面在说,现在我们重点来认识一下脏读
首先我们来认识一下脏读操作,所谓的脏读操作指的是:一个事务读取到了另一个事务未提交的数据
脏读问题指的就是脏读操作带来的问题,即若一个事务读取了另一个事务未提交的数据,但另一个事务后续未提交,直接回滚了,那么先读取的事务拿到的数据就是无效的,基于该数据的操作也可能出错。
比如,银行转账时,事务 A 从账户 A 扣钱但未提交,事务 B 此时读取账户 A 余额,若事务 A 回滚,事务 B 读到的就是错误余额 。
2.读提交【Read Committed】
读提交是什么意思?
意思就是在这种隔离级别下,mysql允许其他事务查看一个事务提交之后更新的数据,但是不允许其他事物查看一个事务未提交时更新的数据
提交之前看不到,提交之后就能看到了
读提交的隔离性下,解决了哪些安全问题?还存在哪些安全问题?
-
读提交的隔离性下,已经不存在脏读问题了(为什么?)
因为在读提交的隔离性下,不允许在一个事务内部查看其他未提交事务的数据 -
但是仍然存在下面两种问题:
不可重复读问题(什么是不可重复读问题?)
幻读问题(什么是幻读问题?)
你可以这么认为,设置读提交的隔离级别,主要就是为了解决脏读问题的,就是不允许其他的事务去读取一个未提交事务更新的数据。一个事务提交之前,他对数据库做的任何更新操作都不允许被其他事务看见。
3.可重复读 【Repeatable Read】
可重复读是什么意思?
什么是不可重复读?
想要理解可重复读,我们可以先理解一下什么叫做不可重复读
注意看这张图的右侧,同一个事务内,同样的读取,在不同的时间段(依旧还在事务操作中!),读取到了不同的值,这种现象叫做不可重复读(non reapeatable read)!!

不可重复读这个概念感觉和幂等这个概念挺像的。所谓幂等指的就是多次执行相同的操作,最终得到的结果与执行一次完全一致。在上图中右端客户端执行了两次相同的查询。操作但是得到的结果却是不同的。那么我们就称此时这个操作就是不幂等的。
什么是可重复读
那可重复读就是指:一个事务,在这个事务的生命周期内的任意时间读取数据库中的数据,得到的结果始终是相同的
可重复读在上一个隔离级别读已提交的基础上,又做了哪些限制?
上一个隔离级别读已提交,不允许其他事务读取一个没有提交的事务中更新的数据。但是如果一个事物已经提交了,此时其他运行中的事务就可以同步到数据库更新之后的新数据了。
可重复读继承了读已提交的约束,不允许事务在运行过程中查看未提交事务更新的数据,在这个基础上又做了进一步的限制:事务是看不到其运行过程中数据库的更新操作的。即一个事务在执行过程中,不允许查看在该事务运行过程中提交的事务的更新数据,该事务在运行过程中看到的,是该事务开始时的老数据库(数据库在该事务启动时的快照)
为什么要引入可重复读这个隔离级别?
原因就在于,上一个级别:读提交,存在着一个不可重复读的问题。可重复读这个隔离级别,就是为了解决不可重复读问题而设置的。那什么是不可重复读问题呢?
什么是不可重复读问题?
现在我有一个场景,公司年终的时候根据每个员工的业绩给员工颁奖,奖品的发放规则如下:
- 将所有贡献值在300到500之间的员工名单筛选出来,给他们每个人发一个台灯
- 将所有贡献值在500到800之间的员工名单筛选出来,给他们每个人发一个键盘
- 将所有贡献值在800到2000之间的员工名单筛选出来,给他们每个人发一个手机
- 将所有贡献值在2000以上的员工名单筛选出来,给他们每个人发一个电脑
在公司内部数据库处理这个奖品发放的业务中,我们需要先把每个奖品对应的员工给分好,然后发奖。对应的事务可能是:
mysql> begin;
mysql> select name from employee WHERE contribution BETWEEN 300 AND 500;
mysql> select name from employee WHERE contribution BETWEEN 500 AND 800;
mysql> select name from employee WHERE contribution BETWEEN 800 AND 2000;
mysql> select name from employee WHERE contribution >= 2000;
mysql> commit;
按照正常情况下,一张表中,每个人都只属于一个奖品区间,都只能获得一份奖品
但是这个时候有个人叫张三的,他想钻空子。张三同志的贡献值为300,当数据库执行第一条筛选语句时,把张三筛上去了,然后给张三发了一个台灯。在数据库执行第二条筛选语句前,张三迅速跑到mysql后台,输入了下面的语句,将给自己的贡献值加到了600
UPDATE employees SET contribution = 600 WHERE name = '张三';
这时候如果mysql的隔离级别是读已提交,虽然不允许事务读取一个未提交事务更新的数据,但是允许事务在其运行过程中看到其他已提交事务更新的数据。还记得我们前面说的吗,在自动提交的模式下,一条sql语句就是一个事务,也就是说发放奖品的那个事务在运行的过程中,是能够看到张三刚刚输入的那条UPDATE语句更新后的数据的!
假如说张三这条UPDATE语句提交的时间要早于发放奖品的那个事务第二句sql语句的执行,那么第二个sql语句执行的时候,他看到的张三的贡献值就是600,这时候它拉出来的第二个名单里,也会有张三。于是公司又给张三发了一个键盘。
同理,在数据库执行第三条筛选语句前,张三迅速跑到mysql后台,给自己的贡献值加到了900…,那第三条筛选语句拉出来的名单里依然有张三,于是公司又给张三发了一个手机。
这样下去最终的结果就是,四个奖品获奖名单中都有张三,张三一个人拿了四个奖品。这就是不可重复读带来的问题!
现在请问:在一个事务内多次读取同一份数据,数据的值保持不变,与在事务结束之后,正常select多次读取同一份数据,数据的值保持不变。这俩说法的区别在哪里?
在事务结束之后,正常select多次读取同一份数据,数据的值保持不变。这是由数据库的持久性保证的
在一个事务内多次读取同一份数据,数据的值保持不变,这是由会话的隔离性(可重复读级别的隔离性)保证的。如果当前访问该数据库的会话,它的隔离性要比可重复读要低,比如说是读提交或者读未提交,那就无法确保
可重复读的隔离性下,解决了哪些安全问题?还存在哪些安全问题?
解决了不可重复读和脏读的问题,但是还存在着幻读问题
注意:其实mysql解决了在可重复读的隔离性下出现的幻读问题,但是在大多数数据库软件中,可重复读的隔离性下都是存在幻读问题的
什么是幻读问题?
幻读(Phantom Read)是数据库事务并发场景中一种特殊的数据一致性问题,指在同一个事务内,两次执行相同的查询语句,却返回了不同的结果集(行数增减),如同出现了 “幻觉”。
具体来说,幻读的典型表现是:
- 事务 A 第一次查询符合条件的记录,得到 N 条结果;
- 事务 B 此时插入 / 删除了一条符合该条件的新记录并提交;
- 事务 A 再次执行相同的查询,结果变成了 N+1 条(或 N-1 条),与第一次结果不一致。
可重复读的隔离级别下,存在幻读问题吗?
请思考下面的问题
我们将一个mysql客户端当前会话的隔离级别设定成可重复读,然后向表stu中插入了一个新的表项(我们把这个操作记为事务1),假如说这时候有另一个事务2正在运行中(该事务启动的时候这个表项是没有的),事务1插入完毕之后事务2进行select * from stu,能查出事务1新插入的表项吗?
可重复读的隔离级别下,数据一致性往往是通过对数据加锁实现的,但是对于一个并不存在的表项,数据库没有办法事先对这个表项中的数据进行加锁,所以有时候没有办法做到完全隔离。在很多数据库软件中,另一端select是能看到的
但是在MYSQL中,可以保证另一端select看不到
也就是说,在MYSQL中,可重复读的隔离条件下不存在幻读问题,但是在绝大多数数据库软件中,可重复读的隔离性下都是存在幻读问题的
为什么可重复读的隔离级别下会存在幻读问题?
这个答案我们在上个问题中说过了,但是为了强调,我们就再说一遍
可重复读的隔离级别下,数据一致性往往是通过对数据加锁实现的,但是对于一个并不存在的表项,数据库没有办法事先对这个表项中的数据进行加锁,所以有时候没有办法做到完全隔离。因此在很多数据库软件中,另一端select是能看到的这个刚刚插入的数据的
为什么MYSQL在可重复读的隔离级别下不会存在幻读问题?
因为MYSQL的数据一致性是通过快照机制实现的,MYSQL会在事务开始时给当前数据库拍一个快照,在RR的隔离级别下,事务在运行过程中访问的数据库,都是数据库在该事务启动时的快照。
因此,即使其他客户端在会话 A 的事务执行过程中插入并提交了新数据,会话 A 的事务也 “看不到” 这些变化,直到当前事务结束(COMMIT 或 ROLLBACK),后续新事务的查询才会基于新的快照。
更具体地来说,MySQL是用Next-Key锁解决的。下面我们就具体来介绍一下
Next-Key 锁 = 间隙锁(Gap Lock) + 行锁(Record Lock)
1. 行锁(Record Lock)
- 作用:锁定表中实际存在的某一行数据,防止其他事务修改或删除这一行。
- 示例:当事务 A 执行
SELECT * FROM stu WHERE id = 10 FOR UPDATE;时,会对id=10的行加行锁,其他事务无法修改或删除id=10的记录。
2. 间隙锁(Gap Lock)
-
作用:锁定不存在实际记录的“间隙区间”,防止其他事务在该区间内插入新记录(这是解决幻读的核心)。
-
间隙的定义:表中相邻两条记录之间的区间,或第一条记录之前、最后一条记录之后的区间。
例如,表中id存在 5、10、15 三条记录,则间隙包括:
(-∞, 5)、(5, 10)、(10, 15)、(15, +∞)。 -
示例:事务 A 执行
SELECT * FROM stu WHERE id BETWEEN 5 AND 15 FOR UPDATE;时,除了对id=5、10、15加行锁,还会对间隙(5,10)、(10,15)加间隙锁,阻止其他事务在这些区间插入新记录(如id=7、12等)。
3. Next-Key 锁(组合效果)
Next-Key 锁是行锁与间隙锁的合并,它会锁定一个包含实际记录和前后间隙的连续区间。
- 对于
id=10这条记录,Next-Key 锁的范围是(5, 10](左开右闭),既锁定id=10这一行,也锁定5~10之间的间隙。 - 当执行范围查询(如
WHERE id > 8 AND id < 12)时,Next-Key 锁会覆盖整个查询范围,阻止区间内的修改和插入。
Next-Key 锁如何解决幻读?
以具体案例说明:
假设 stu 表中 id 为 5、10、15,事务 A 和 B 操作如下:
-
事务 A(RR 级别):
START TRANSACTION; -- 查询并锁定 score > 80 的记录(假设对应 id=10、15) SELECT * FROM stu WHERE score > 80 FOR UPDATE;- InnoDB 会对
id=10、15加行锁; - 同时对
score > 80对应的间隙(如(10,15)、(15, +∞))加间隙锁。
- InnoDB 会对
-
事务 B:
-- 尝试插入一条 score=85 的新记录(id=12,属于事务 A 锁定的间隙) INSERT INTO stu (id, score) VALUES (12, 85);- 由于
id=12落在事务 A 锁定的间隙(10,15)中,事务 B 会被阻塞,等待事务 A 释放锁。
- 由于
-
事务 A 继续执行:
-- 再次查询,结果与第一次一致(无新增记录) SELECT * FROM stu WHERE score > 80; COMMIT; -- 释放所有锁,事务 B 此时才能执行插入
通过阻止事务 B 插入符合条件的新记录,事务 A 在整个事务中始终看到一致的结果集,从而解决了幻读。
Next-Key 锁使用注意事项
- Next-Key 锁仅在 RR 级别生效:Next-Key 锁是 InnoDB 在 RR 级别下的特有机制,读已提交(RC)级别会禁用间隙锁(仅用行锁),因此 RC 级别可能出现幻读。
- Next-Key 锁通过索引锁定某个左开右闭区间:间隙锁基于索引生效,若查询未使用索引(全表扫描),会锁定整个表的所有间隙,导致并发性能下降。
- Next-Key 锁通过范围查询触发:通常在使用
SELECT ... FOR UPDATE(写锁)或SELECT ... LOCK IN SHARE MODE(读锁)的范围查询时触发,普通SELECT(快照读)不会加锁,但依赖一致性快照避免幻读。
4.串行化【serializable】
什么是串行化?
就是对所有操作都加锁,将并发的多进程/线程彻底串行化
但是效率非常低很低,几乎完全不会被采用
串行化的隔离条件下,两个事务访问同一张表的表现
串行化如何控制表访问?
在串行化的隔离条件下,当多事务同时访问同一张表时,第一个访问的事务要给这张表上一把共享锁(S 锁,允许共享读,但不允许写),上了之后如果有其他事务想要修改这个表,那他就会陷入阻塞,直到这个读锁被释放。同样地,当事务想要通过INSERT/UPDATE/DELETE语句修改这张表,或者通过SELECT … FOR UPDATE 对表进行加锁读操作时,就会给这张表上一把排他锁(X 锁)。上锁之后,其余事务再想修改或者加锁读,就会陷入阻塞,直到X锁释放为止。
那上面一段中说的S锁和X锁,都是什么时候才会释放呢?这些锁的生命周期会不会受不同的隔离级别影响呢?
无论是 X 锁还是 S 锁,只要是 “事务主动或隐式获取” 的锁,其持有周期完全跟随事务 —— 从锁被获取(语句执行时)到事务结束(提交 / 回滚),锁才会释放。不管是什么隔离级别,都是一样的
除了串行化之外,其他的隔离级别还会用到X锁与S锁吗
除了串行化隔离级别,其他隔离级别(如 读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read))也会频繁用到 X锁(排他锁) 和 S锁(共享锁)——核心区别在于“是否主动/隐式加锁”,而非“是否使用锁”。X锁和S锁是数据库实现并发控制的基础机制,所有隔离级别(包括非串行化)在处理“修改操作”或“显式加锁读”时,都会依赖这两种锁。
(1)各隔离级别对X锁的使用:完全一致(必用)
X锁用于保障“修改操作的原子性”(防止同一行数据被多个事务同时修改),所有隔离级别下,只要执行修改操作(UPDATE/DELETE/INSERT),或显式加锁读(SELECT ... FOR UPDATE),都会为目标数据加X锁,且锁的生命周期均为“事务结束(提交/回滚)释放”,与隔离级别无关。
| 隔离级别 | 触发X锁的典型操作 | 锁的核心作用 |
|---|---|---|
| 读未提交(RU) | UPDATE user SET age=20 WHERE id=1 | 阻止其他事务同时修改id=1的行,避免“脏写”(即使RU允许读未提交数据,也禁止脏写)。 |
| 读已提交(RC) | DELETE FROM user WHERE id=2 | 确保删除操作的原子性,其他事务需等待锁释放后才能修改/删除id=2的行。 |
| 可重复读(RR) | SELECT balance FROM user WHERE id=3 FOR UPDATE | 显式加X锁,后续事务无法加S锁/X锁,保障当前事务对id=3的行有“独占修改权”。 |
| 串行化(SERIALIZABLE) | INSERT INTO order (user_id) VALUES (4) | 对插入的新行加X锁,防止其他事务修改该新行(与其他级别逻辑一致)。 |
结论:X锁是“修改操作的标配”,所有隔离级别都必须依赖X锁防止“脏写”,使用逻辑完全一致。
(2)各隔离级别对S锁的使用:差异在“是否隐式加锁”
S锁用于保障“读操作的一致性”(多个事务可同时读,但禁止写),不同隔离级别对S锁的使用差异,体现在“普通SELECT是否隐式加S锁”——仅串行化级别会对普通SELECT隐式加S锁,其他级别需“显式声明”才会加S锁,但所有级别都支持通过显式语法加S锁。
- 非串行化级别:仅“显式加锁读”才会用S锁
读未提交(RU)、读已提交(RC)、可重复读(RR)这三个级别,普通SELECT默认是“无锁快照读”(依赖MVCC机制实现),不会主动加S锁;但如果通过显式语法声明“加共享锁读”,则会为目标数据加S锁,且锁生命周期仍为“事务结束释放”。
| 隔离级别 | 是否为普通SELECT加S锁 | 触发S锁的典型操作(显式) | 锁的核心作用 |
|---|---|---|---|
| 读未提交(RU) | 否(快照读) | SELECT name FROM user WHERE id=1 FOR SHARE | 允许其他事务加S锁(共同读),但禁止加X锁(修改),避免读时被修改。 |
| 读已提交(RC) | 否(快照读) | SELECT age FROM user WHERE id=2 LOCK IN SHARE MODE(MySQL语法) | 确保当前事务读的是“已加锁的一致性数据”,其他事务修改需等待S锁释放。 |
| 可重复读(RR) | 否(快照读) | SELECT balance FROM user WHERE id=3 FOR SHARE | 结合RR的MVCC,既保证“可重复读”,又通过S锁阻止其他事务修改,避免“不可重复读”。 |
| 串行化(SERIALIZABLE) | 是(隐式加锁) | 普通SELECT * FROM user WHERE id=4 | 对所有普通读隐式加S锁,强制“读-写”串行化,彻底避免不可重复读和幻读。 |
- 关键差异:为何非串行化级别不隐式加S锁?
为了平衡“一致性”和“并发性能”:
- 串行化级别通过“隐式加S锁”强制读-写串行,保障最高一致性,但会降低并发(读会阻塞写,写也会阻塞读);
- 非串行化级别(尤其是RC、RR)通过“MVCC快照读”替代“隐式S锁”——普通读无需加锁,直接读取版本化的快照数据,既避免了锁竞争,又能满足当前隔离级别的一致性需求(如RC避免脏读,RR避免不可重复读)。
- 只有当用户明确需要“读锁保护”(如“读后立即写,防止中间被修改”)时,才通过
FOR SHARE显式加S锁,按需平衡一致性和性能。
(3)总结:各隔离级别与X/S锁的关系
| 锁类型 | 读未提交(RU) | 读已提交(RC) | 可重复读(RR) | 串行化(SERIALIZABLE) | 核心规律 |
|---|---|---|---|---|---|
| X锁 | 用(修改/显式加锁读) | 用(修改/显式加锁读) | 用(修改/显式加锁读) | 用(修改/显式加锁读) | 所有级别必用,用于防止脏写,释放时机均为事务结束。 |
| S锁 | 仅显式加锁读用 | 仅显式加锁读用 | 仅显式加锁读用 | 显式加锁读+普通读隐式用 | 差异在“普通读是否隐式加锁”,显式加锁读的逻辑所有级别一致,释放时机均为事务结束。 |
简言之:X锁是“修改操作的基础”,所有隔离级别都离不开;S锁是“读一致性的工具”,非串行化级别按需显式使用,串行化级别强制隐式使用——但无论哪种情况,X锁和S锁的核心机制(加锁逻辑、释放时机)在所有隔离级别下都是统一的。
实际案例:两个事务访问同一张表的表现
假设存在 stu 表,事务 A 和事务 B 分别在两个 MySQL 客户端中执行操作,隔离级别均为 串行化:
案例 1:事务 A 先执行读操作,事务 B 后执行写操作
-
事务 A(客户端 1):
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- 设为串行化 START TRANSACTION; SELECT * FROM stu; -- 普通读(非加锁) -- 此时事务 A 未提交,持有“一致性快照” -
事务 B(客户端 2):
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; START TRANSACTION; INSERT INTO stu (name, age) VALUES ('张三', 20); -- 尝试写操作 -- 结果:事务 B 被阻塞,终端显示“Waiting for table metadata lock”或“Waiting for row lock” -
后续操作:
- 若事务 A 执行
COMMIT;(提交),事务 B 的插入操作会立即执行; - 若事务 A 执行
ROLLBACK;(回滚),事务 B 的插入操作也会解除阻塞并执行。
- 若事务 A 执行
案例 2:事务 A 先执行写操作,事务 B 后执行读操作
-
事务 A(客户端 1):
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; START TRANSACTION; INSERT INTO stu (name, age) VALUES ('李四', 22); -- 写操作,加行锁+间隙锁 -- 未提交 -
事务 B(客户端 2):
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; START TRANSACTION; SELECT * FROM stu FOR UPDATE; -- 加锁读操作 -- 结果:事务 B 被阻塞,等待事务 A 释放锁 -
后续操作:
- 事务 A 提交后,事务 B 的查询会返回包含“李四”的完整结果;
- 事务 A 回滚后,事务 B 的查询会返回不含“李四”的结果。
总结
在串行化隔离级别下,两个事务允许访问同一张表,但访问会被 InnoDB 的锁机制强制“串行化”:一个事务的操作未结束前,另一个事务的写操作或加锁读操作会被阻塞。这种机制确保了最高的隔离性,但仅适合并发量极低、对数据一致性要求极高的场景(如金融核心交易的对账逻辑),多数业务场景更倾向于使用默认的 RR 级别。
串行化的隔离性下,解决了哪些安全问题?还存在哪些安全问题?
在串行化的隔离性下,脏读问题、不可重复读问题和幻读问题都得到了解决,基本不存在安全问题了
MYSQL中锁的分类
在MySQL中,锁的“等级”(通常更准确的表述是锁的粒度和锁的作用范围)确实存在分层,但核心分类是 “锁粒度”(表级锁、行级锁)和 “锁的作用对象”(事务锁、元数据锁等),而非简单的“表级事务级”——“事务级”更偏向描述锁的“生命周期与事务绑定”,而非独立的锁等级。
下面从 锁粒度(核心分层)、锁作用范围(事务/元数据等)两个维度,结合MySQL的具体实现,系统梳理锁的分类体系:
核心分层:按“锁粒度”划分(表级锁 vs 行级锁)
锁粒度决定了“锁控制的范围大小”——粒度越大,并发性能越低(锁冲突概率高),但锁开销越小(管理简单);粒度越小,并发性能越高(锁冲突概率低),但锁开销越大(需管理更多锁实例)。MySQL同时支持这两种粒度,不同存储引擎(如InnoDB、MyISAM)对锁的支持不同(MyISAM仅支持表级锁,InnoDB同时支持表级锁和行级锁)。
1. 表级锁(Table-Level Lock)
-
定义:锁定整个表,对表中所有行的操作都会受影响。
-
特点:开销小、加锁快、无死锁风险,但锁冲突概率极高(如一个事务锁表后,其他事务无法修改表中任何行),并发性能差。
-
MySQL支持的表级锁类型:
- 表级共享锁(Table Shared Lock, TS):允许持有锁的事务读表,禁止写表;多个事务可同时持有TS锁(读不互斥)。
示例:LOCK TABLES user READ;(显式加表级S锁,事务结束或执行UNLOCK TABLES释放)。 - 表级排他锁(Table Exclusive Lock, TX):允许持有锁的事务读写表,禁止其他事务读/写表(读写都互斥)。
示例:LOCK TABLES user WRITE;(显式加表级X锁,释放时机同上)。 - 意向锁(Intention Lock):InnoDB特有的“表级辅助锁”,用于“提前声明事务的加锁意图”,避免表级锁与行级锁的冲突。
- 意向共享锁(IS):事务计划对表中某些行加行级S锁前,先加IS锁(表级);
- 意向排他锁(IX):事务计划对表中某些行加行级X锁前,先加IX锁(表级)。
意向锁仅用于“锁冲突判断”,不会阻塞普通的读/写操作(例如,IS锁与其他表级IS/IX锁兼容,仅与TX锁冲突)。
- 表级共享锁(Table Shared Lock, TS):允许持有锁的事务读表,禁止写表;多个事务可同时持有TS锁(读不互斥)。
-
典型场景:MyISAM引擎的所有写操作(
UPDATE/DELETE)默认加表级X锁;InnoDB在执行ALTER TABLE(修改表结构)、LOCK TABLES(显式锁表)时会加表级锁。
2. 行级锁(Row-Level Lock)
-
定义:仅锁定表中“满足条件的行”,未锁定的行仍可被其他事务读写。
-
特点:开销大、加锁慢、可能产生死锁(多个事务互相等待对方的行锁),但锁冲突概率极低,并发性能极高——是InnoDB支持高并发的核心基础。
-
MySQL(InnoDB)支持的行级锁类型:
- 行级共享锁(Row Shared Lock, RS):即之前提到的 S锁,允许持有锁的事务读该行,禁止其他事务加X锁(可加S锁)。
触发方式:SELECT ... FOR SHARE(或LOCK IN SHARE MODE,MySQL语法)。 - 行级排他锁(Row Exclusive Lock, RX):即之前提到的 X锁,允许持有锁的事务读写该行,禁止其他事务加S锁/X锁(完全互斥)。
触发方式:UPDATE/DELETE/INSERT(隐式加锁)、SELECT ... FOR UPDATE(显式加锁)。 - 间隙锁(Gap Lock)与临键锁(Next-Key Lock):InnoDB在 可重复读(RR) 隔离级别下,为防止“幻读”引入的特殊行级锁(锁定“行之间的间隙”而非具体行):
- 间隙锁:锁定“不存在的行范围”(如
WHERE id BETWEEN 10 AND 20,若表中无id=15的行,会锁定10-20的间隙),阻止其他事务插入该范围的行; - 临键锁:间隙锁+行锁的组合(默认锁模式),既锁定行,也锁定行前后的间隙,彻底避免幻读。
- 间隙锁:锁定“不存在的行范围”(如
- 行级共享锁(Row Shared Lock, RS):即之前提到的 S锁,允许持有锁的事务读该行,禁止其他事务加X锁(可加S锁)。
-
典型场景:InnoDB引擎的所有行修改/显式加锁读操作(如
UPDATE user SET age=20 WHERE id=1),默认加行级X锁;SELECT ... FOR SHARE加行级S锁。
按“锁的作用范围”划分:事务锁 vs 元数据锁
除了“粒度”,还可按“锁保护的对象”划分,核心包括 事务锁(保护数据修改的原子性)和 元数据锁(保护表结构的一致性):
1. 事务锁(Transaction Lock)
- 定义:与事务绑定的锁,生命周期为“事务开始→事务提交/回滚”,用于保障事务的ACID特性(尤其是原子性、隔离性)。
- 包含的锁类型:
- 所有行级锁(S锁、X锁、间隙锁、临键锁);
- 表级锁中的“意向锁(IS/IX)”“显式表级S/X锁(
LOCK TABLES)”。
- 核心特点:仅在事务内生效,事务结束后自动释放,是处理“数据并发修改”的核心锁类型(之前讨论的X锁、S锁均属于此类)。
2. 元数据锁(Metadata Lock, MDL)
- 定义:保护“表结构元数据”(如表名、列名、索引结构)的锁,与事务无关,用于避免“表结构修改”与“数据读写”的冲突。
- 触发场景:
- 当执行
SELECT/INSERT/UPDATE/DELETE等“数据操作”时,MySQL会自动加 MDL读锁(多个事务可同时加,不互斥); - 当执行
ALTER TABLE/DROP TABLE/RENAME TABLE等“表结构修改操作”时,MySQL会自动加 MDL写锁(仅允许一个事务加,与读锁、写锁都互斥)。
- 当执行
- 核心特点:
- 无需显式声明,MySQL自动管理;
- MDL读锁会阻塞MDL写锁(例如,若有事务正在执行
SELECT,则ALTER TABLE会被阻塞,直到所有SELECT结束); - MDL写锁会阻塞所有MDL读锁/写锁(例如,
ALTER TABLE执行时,所有后续的SELECT都会被阻塞,直到ALTER TABLE结束)。 - 生命周期:MDL读锁在“语句执行结束后释放”(非事务结束);MDL写锁在“表结构修改操作完成后释放”。
MySQL锁的分类体系总结
为了更清晰地理解,用表格梳理核心分类维度:
| 划分维度 | 具体锁类型 | 核心作用 | 典型存储引擎支持 |
|---|---|---|---|
| 按锁粒度 | 1.表级锁 | 锁定整个表,开销小、并发低 | MyISAM(仅支持)、InnoDB |
| 1.1表级S锁(TS)、表级X锁(TX) | 控制表级读写互斥 | ||
| 1.2 意向锁(IS/IX) | 辅助行级锁,避免表级锁冲突 | InnoDB特有 | |
| 2.行级锁 | 锁定单行/间隙,开销大、并发高 | InnoDB特有 | |
| 2.1 行级S锁、行级X锁 | 控制行级读写互斥 | ||
| 2.2 间隙锁、临键锁 | 防止幻读(RR隔离级别) | ||
| 按作用范围 | (1)事务锁 | 保障事务ACID,与事务绑定 | InnoDB(核心)、MyISAM |
| (1-1) 行级S/X锁、意向锁、显式表级S/X锁 | 保护数据修改一致性 | ||
| (2)元数据锁(MDL) | 保护表结构元数据,与事务无关 | 所有存储引擎(MySQL层) | |
| (2-1) MDL读锁(数据操作触发)、MDL写锁(表结构修改触发) | 避免表结构与数据操作冲突 |
特别注意
- “事务级锁”不是独立等级:“事务级”描述的是“锁的生命周期与事务绑定”,而非“锁的粒度”——例如,行级S锁、表级意向锁都是“事务级锁”,但它们的粒度不同。
- InnoDB优先用行级锁:InnoDB虽然支持表级锁,但默认会优先使用行级锁(仅锁定修改的行),以提升并发性能;只有在无法用行级锁的场景(如
ALTER TABLE)才会用表级锁。 - MyISAM无行级锁:MyISAM引擎仅支持表级锁,因此在高并发写场景下性能极差(一个写操作会锁整个表),目前已逐渐被InnoDB取代。
不同的隔离级别的安全问题
不同隔离级别分别能解决哪些安全问题?
其实不同的隔离级别,就是为了解决一个个的安全问题而递进设置的
比如最开始的读未提交,在这个隔离性下,存在着脏读、不可重复读还有幻读问题
为了解决脏读问题,我们对读未提交做了进一步的约束,不允许其他事务查看一个事务未提交时更新的数据,将隔离级别升级成了读提交。升级成读提交之后,虽然解决了脏读问题,但是依然存在着不可重复度和幻读的问题
为了解决不可重复读问题,我们对读提交做了进一步的约束,规定一个事务在运行过程中,只能访问事务开始时数据库的历史版本,不让事务看到在其运行过程中数据库的更新操作。由于隔离级别就进一步升级成了可重复读。可重复读的隔离级别下,脏读与不可重复读的问题都解决了,但是依然存在着幻读问题
为了解决幻读问题,我们又对可重复提交做了进一步的限制,只要两个事务访问,最终变成了可串行化。
我们一般事务默认的隔离级别是哪一级?
RR:可重复读
事务的一致性
对事物的隔离性有了初步了解之后,我们再来回头看一下事务的一致性
如何理解一致性中的一致?与之对应的不一致又如何理解呢?
事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库只包含事务成功提交的结果时,数据库处于一致性状态。如果系统运行发生中断,某个事务尚未完成而被迫中断,而改未完成的事务对数据库所做的修改已被写入数据库,此时数据库就处于一种不正确(不一致)的状态。
为了实现一致性,防止改未完成的事务对数据库所做的修改已被写入数据库,我们就引入了回滚机制,用事务的原子性保证,如果事务最后没有执行完成,那么就会将数据库回滚到事务执行前的状态
事务的一致性是如何实现的?
其实一致性和用户的业务逻辑强相关,一般MySQL提供技术支持,但是一致性还是要用户业务逻辑做支撑
总结来说就是,技术上,通过AID(原子性、隔离性、持久性)支持一致性的实现,但最终一致性的实现,需要由用户的具体操作实现的
9314



被折叠的 条评论
为什么被折叠?



