MySQL 事务隔离级别(学习笔记)
关于事务的隔离(Transaction Isolation):
主要是事务间的“读”隔离,(数据可见性)
这里的“读”并非指的是 select,比如要 update、delete 某一条数据的时候,首先要做的工作就是将数据读出来。
1. 事务隔离级别
MySQL 事务之间的隔离有四个级别:
- read-uncommitted
- read-committed
- repeatable-read
- serializeable
查看隔离级别
SELECT @@tx_isolation;
SELECT @@transaction_isolation; # MySQL 8
如下图:

设置隔离级别
SET GLOBAL transaction_isolation='read-uncommitted';
SET GLOBAL TRANSACTION ISOLATON LEVEL read uncommitted;
# 设置可能需要重新登录才生效
如下图:

2. 读未提交(read uncommitted,RU)
2.1 read uncommitted
级别最低的隔离
事务 A 还没有结束,但其未提交的数据,能够被事务 B 读取到。
示例如下:
事务 A | 事务 B | |
---|---|---|
begin; | ||
start transaction; | ||
select * from table_xxx; | ||
insert into table_xxx values(yyy); | ||
select * from table_xxx; | 能够看到 yyy | |
rollback; |
如上,
事务 A 中,插入了 yyy,没有进行提交,
此时事务 B 查询的时候,能够看到事务 A 插入的这条数据,
这就是脏读(dirty read)
2.2 read uncommitted 的脏读
read uncommitted 带来的问题:脏读,Dirty Read
事务 A 将数据由 m 修改为 n,还未提交,此时数据被事务 B 读取,得到 n;
事务 A 结束,进行了回滚,数据还原为 m,但是事务 B 得到的却是 n
其次 read uncommitted 还有不可重复读、幻读,
但大多数数据库的隔离都不会使用这个级别,所以到此为止。
3. 读已提交(read committed,RC)
3.1 read committed
事务 A 修改的数据在提交后,才能被事务 B 读取。
如下:
事务 A | 事务 B | |
---|---|---|
begin; | ||
start transaction; | ||
insert into table_xxx values(yyy); | ||
select * from table_xxx; | 不能看到 yyy | |
commit; | ||
select * from table_xxx; | 能看到 yyy |
示例如下

如上,
两个会话的隔离级别都是 READ-COMMITTED
两个会话先后启动事务,
事务 A 插入数据后,还未提交前,自己能读取到新插入数据,但是 事务 B 无法读取到事务 A 未提交的数据
事务 A 提交数据后,事务 B 能顺利读取到提交的数据。
3.2 read committed 解决的问题
-
解决了脏读问题,读到的数据绝对真实。
3.3 read committed 的问题
3.3.1 问题 1:不可重复读(Non Repeatable Read)
https://dev.mysql.com/doc/refman/8.0/en/glossary.html#glos_non_repeatable_read
The situation when a query retrieves data, and a later query within the same transaction retrieves what should be the same data, but the queries return different results (changed by another transaction committing in the meantime).
不可重复读重点在于事务生命周期内,之后的同一个查询,数据是否被改变了。
也就是:在事务 A 中进行一次查询,之后事务 B 进行了 update 或者是 delete 操作,
事务 A 再次进行同一个查询,
因为事务 B 的操作,导致事务 A 第一次读取到的数据和第二次读取到的数据不一致。
这便是不可重复读。
示例如下:

如上,
事务 A、B 先后开启,
事务 A 查询到 lisi,事务 B 对这一条记录进行了修改,lisi → zhangsan,并进行了提交。
事务 A 再次查询,得到的结果是 lisi,
由此,事务 A 中同一个查询先后两次,得到两个不同的结果
其实不可重复读在大部分场景下还是可以容忍的
3.3.2 问题 2:幻读(phantom read)
https://dev.mysql.com/doc/refman/8.0/en/glossary.html#glos_phantom
A row that appears in the result set of a query, but not in the result set of an earlier query.
For example, if a query is run twice within a transaction, and in the meantime, another transaction commits after inserting a new row or updating a row so that it matches the WHERE clause of the query.
也就是:
事务 A 中有一个查询:select xxx from table where yyy;
接下来,事务 B 进行了一个 insert 语句或 update 语句,并进行了提交。
这使得事务 A 的查询语句结果集发生了变化:insert 的记录或者 update 的记录匹配了 where yyy
由此事务 A 前后两次的查询结果集不一样
这种情况就是幻读
示例如下:

如上,
(1)(2)事务 A、B 先后开启,
(3)事务 A 查询 yunnan 人口大于 10w 的城市,得到 kunming、gejiu、qujing、dali 4 条记录,
(4)随后事务 B 将 Kaiyuan 的人口修改为 12w,并进行了提交。
(5)此时事务 A 再次查询,Kaiyuan 出现在了结果集中,而之前的结果集中并没有 Kaiyuan
3.3.3 不可重复读和幻读的区别
首先,两者都是前后读取到的数据不一致。
对比幻读和不可重复读:
-
不可重复读重点在于:(记录?)数据是否被改变了
-
在一个事务中对同一条记录进行查询,第一次读取到的数据和第二次读取到的数据不一致。
-
引起不可重复读的原因在于另一个事务进行了 update 或者是 delete 操作。
-
-
幻读重点在于:数据是否存在
-
原本不存在于第一次结果集中的数据,出现在了第二次结果集中
-
对比上图的例子,事务 B 的 update 操作,导致 Kaiyuan 出现在了结果集中
-
导致幻读的原因是其它事务的 insert、update 操作。(another transaction commits after inserting a new row or updating a row so that it matches the WHERE clause of the query. )
-
4. 可重复读(repeatable read,RR)
4.1 repeatable read
MySQL 的默认隔离级别
事务 A 开始后,在结束前,对于某个表的数据读取,(事务 A 没有修改的情况下)永远都是第一次读取时候的状态(并非开启时候的状态),哪怕后续其他事务对这个表已经提交了修改。
可以理解为:事务 A 开始 select 的时候 做了一个快照,事务 A 生命周期内,读取到的数据都是快照时候的状态,哪怕 事务 B 对数据进行了修改。
也就是:
-
事务 A 开始后,此时 SELECT * FROM table_name 为状态 m,
-
接下来,事务 B 对这个表的数据进行了增删改,并进行了提交,SELECT * FROM table_name 的状态变为 n
-
但这个时候,事务 A 还未结束,再次 SELECT * FROM table_name 的状态依旧为 m,也就是事务 A 开始 select 时的状态
如下:
事务 A | 事务 B | |
---|---|---|
begin; | ||
start transaction; | ||
select * from table_xxx; | ||
insert into table_xxx values(yyy); | ||
commit; | ||
select * from table_xxx; | 不能看到 yyy | |
commit; | ||
select * from table_xxx; | 能看到 yyy |
示例如下:

如上,这个级别下,
事务 A 开启后进行了 select,这个时候就会有一个快照,
之后是事务 B 中插入了数据,并提交。
但是事务 A 无法读取到事务 B 对表已经提交的修改,只能读取到快照的数据。
当事务 A 结束后,才能看到事务 B 所做的修改
4.2 快照的时间点
上面说,快照是第一个 select 的状态,示例如下:
如上,
(1)(2)事务 A、B 先后开启,
(3)开启后,事务 A 中并没有进行 select 语句,
(4)而是先在事务 B 中先进行了 update,并提交
(5)之后,事务 A 才进行 select,可以看到,读取到了事务 B 进行的修改,
由此可见,快照并非是事务开启时候的快照,而是第一个 select 的快照。
即便之后事务 B 再进行修改、提交,事务 A 也无法读取到
4.3 repeatable read 解决的问题
-
repeatable read 解决了不可重复读的问题,事务周期内看到的数据都一样
也会有幻读的情况,见下一部分
4.4 repeatable read 的幻读
repeatable read 也会有幻读的情况
4.4.1 快照读 和 当前读
快照读 读取的是快照数据。
不加锁的简单的 select 都属于快照读,如:
SELECT * FROM xxx WHERE ...
前面的例子都是快照读,
再次示例如下:

当前读 就是读取最新数据,而不是历史版本的数据。
加锁的 select,或者对数据进行增删改都会进行当前读。(增删改之前需要先读取),如:
SELECT * FROM xxx LOCK IN SHARE MODE;
SELECT * FROM xxx FOR UPDATE;
INSERT INTO xxx values ...
DELETE FROM xxx WHERE ...
UPDATE xxx SET ...
4.4.2 repeatable read 的幻读
在 repeatable read 隔离级别下:
普通的查询是快照读,是不会看到别的事务插入的数据的。(但是数据可能已经有了更新)
当前读 会读取最新的数据,所以 当前读 的时候,结果集有可能会变化,产生幻读
示例如下:

如上,
(1)(2)事务 A、B 先后开启,
(3)之后事务 A 中开启普通 select,此时会有一个快照,
(4)事务 B 中插入了一条数据,并进行了提交
(5)事务 A 先进行了一个普通 select,可以看到和第一次 select 结果一样,是一个快照读
紧接着进行了一次 select ... for update,可知这是一个 当前读,会读取真实的数据,
由此,第 3 次和 第 1、2 次的 select 结果集相比较,多出了一条数据,
由此产生了幻读
4.4.3 加锁的情况
repeatable read 下,会通过一些锁(比如说间隙锁)来解决幻读的情况,
当事务 A 执行 select xxx for update 的查询时,会给 表上锁, (或者执行一个 update、delete 之类的操作,先不提交,前面提到,这些都是当前读)
紧接着,如果事务 B 要进行数据的插入、修改,就会被阻塞,
如此就避免了幻读的情况,但是造成了阻塞,限制了并发
示例如下:

如上,
(1)(2)事务 A、B 先后开启
(3)(忘了标编号)事务 A 中,进行了一个加锁的读取,select ... for update
(4)(上图中的 3 号)随后,事务 B 中想插入一条数据,回车之后,红色箭头处会有一个等待的时间,直到超时报错
(具体加锁的内容,后续再补)
5. 序列化 / 串行化(serializable,SE)
最高的隔离级别
事务需要排队,不能并发
读取到最真实的数据,效率也最低
如:
事务 A | 事务 B | |
---|---|---|
begin; | ||
start transaction; | ||
insert into table_xxx values(yyy); | ||
select * from table_xxx; | 卡住,在排队 | |
commit; | ||
排队的命令执行 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构