你想要一份关于 MySQL 事务隔离级别的详细教程,重点理解 READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ(MySQL 默认)、SERIALIZABLE 这四种隔离级别的含义、区别、问题场景和实际用法,我会用通俗易懂的语言+实操案例帮你彻底搞懂这个核心知识点。
一、前置基础:为什么需要事务隔离级别?
事务隔离级别是为了解决多事务并发执行时出现的脏读、不可重复读、幻读三大问题,不同隔离级别对这三类问题的解决能力不同,同时也会影响数据库的并发性能(隔离级别越高,并发性能越低)。
先明确三个核心问题的定义:
- 脏读:一个事务读取到另一个事务未提交的数据(比如A转账给B,事务没提交,B却看到余额增加了,之后A回滚,B看到的就是“脏数据”)。
- 不可重复读:同一个事务内,多次读取同一数据,结果不一致(比如A事务第一次读余额是100,期间B事务修改并提交了余额为200,A再读就变成200)。
- 幻读:同一个事务内,多次执行同一查询(比如统计符合条件的行数),结果行数不一致(比如A事务统计有10条订单,期间B事务插入了1条新订单并提交,A再统计就变成11条)。
二、MySQL 四大事务隔离级别详解
MySQL 中可以通过以下命令查看/设置隔离级别:
-- 查看当前会话的隔离级别(MySQL 8.0+)
SELECT @@TRANSACTION_ISOLATION;
-- 查看全局隔离级别
SELECT @@GLOBAL.TRANSACTION_ISOLATION;
-- 设置当前会话的隔离级别(临时生效)
SET SESSION TRANSACTION ISOLATION LEVEL [隔离级别];
-- 设置全局隔离级别(永久生效,需重启MySQL)
SET GLOBAL TRANSACTION ISOLATION LEVEL [隔离级别];
下面逐个讲解四大隔离级别,所有案例都基于以下测试表:
-- 先创建测试表并插入数据
CREATE TABLE account (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(20),
balance INT
);
INSERT INTO account (name, balance) VALUES ('张三', 1000);
1. READ UNCOMMITTED(读未提交)
- 核心特点:最低隔离级别,允许一个事务读取另一个事务未提交的数据。
- 解决问题:无(会出现所有并发问题)。
- 存在问题:脏读、不可重复读、幻读都可能发生。
- 实操案例:
- 会话1(事务A):开启事务,修改数据但不提交
-- 会话1 SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; START TRANSACTION; -- 开启事务 UPDATE account SET balance = 2000 WHERE name = '张三'; -- 不执行COMMIT,事务未提交 - 会话2(事务B):读取数据,能看到未提交的修改
-- 会话2 SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; START TRANSACTION; SELECT balance FROM account WHERE name = '张三'; -- 结果是2000(脏数据) - 会话1回滚事务:
-- 会话1 ROLLBACK; -- 回滚修改 - 会话2再读:
-- 会话2 SELECT balance FROM account WHERE name = '张三'; -- 结果变回1000,出现脏读
- 会话1(事务A):开启事务,修改数据但不提交
- 适用场景:几乎不用(仅适用于对数据准确性要求极低、追求极致并发的场景)。
2. READ COMMITTED(读已提交)
- 核心特点:只能读取到其他事务已提交的数据,解决了脏读问题。
- 解决问题:脏读。
- 存在问题:不可重复读、幻读。
- 实操案例(不可重复读):
- 会话1(事务A):开启事务,第一次读取数据
-- 会话1 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; START TRANSACTION; SELECT balance FROM account WHERE name = '张三'; -- 结果:1000 - 会话2(事务B):修改并提交数据
-- 会话2 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; START TRANSACTION; UPDATE account SET balance = 2000 WHERE name = '张三'; COMMIT; -- 提交事务 - 会话1(事务A):同一事务内再次读取
-- 会话1 SELECT balance FROM account WHERE name = '张三'; -- 结果:2000(不可重复读) COMMIT;
- 会话1(事务A):开启事务,第一次读取数据
- 适用场景:Oracle、SQL Server 默认隔离级别,适合对脏读敏感、能接受不可重复读的业务(比如普通查询场景)。
3. REPEATABLE READ(可重复读,MySQL InnoDB 默认)
- 核心特点:同一个事务内,多次读取同一数据,结果始终一致(即使其他事务修改并提交),解决了脏读、不可重复读问题。
- 解决问题:脏读、不可重复读。
- 存在问题:理论上存在幻读(但 MySQL InnoDB 通过 MVCC + 间隙锁解决了幻读,实际使用中几乎不会遇到)。
- 实操案例(解决不可重复读):
- 会话1(事务A):开启事务,第一次读取数据
-- 会话1 SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; START TRANSACTION; SELECT balance FROM account WHERE name = '张三'; -- 结果:1000 - 会话2(事务B):修改并提交数据
-- 会话2 SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; START TRANSACTION; UPDATE account SET balance = 2000 WHERE name = '张三'; COMMIT; -- 提交事务 - 会话1(事务A):同一事务内再次读取
-- 会话1 SELECT balance FROM account WHERE name = '张三'; -- 结果仍为1000(可重复读) COMMIT; -- 提交后再读,才会看到2000 SELECT balance FROM account WHERE name = '张三'; -- 结果:2000
- 会话1(事务A):开启事务,第一次读取数据
- 适用场景:MySQL 默认级别,适合绝大多数业务场景(电商、后台管理系统等),兼顾一致性和并发性能。
4. SERIALIZABLE(串行化)
- 核心特点:最高隔离级别,所有事务串行执行(加锁,同一时间只有一个事务能操作数据),解决所有并发问题。
- 解决问题:脏读、不可重复读、幻读(全部解决)。
- 存在问题:并发性能极差(相当于单线程操作),容易出现锁等待、死锁。
- 实操案例(解决幻读):
- 会话1(事务A):开启事务,统计符合条件的行数
-- 会话1 SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; START TRANSACTION; SELECT COUNT(*) FROM account WHERE balance > 500; -- 结果:1(只有张三的1000) - 会话2(事务B):尝试插入新数据(会被阻塞,直到会话1提交/回滚)
-- 会话2 SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE; START TRANSACTION; INSERT INTO account (name, balance) VALUES ('李四', 800); -- 此处会卡住,直到会话1提交/回滚 - 会话1提交后,会话2的插入才会执行:
-- 会话1 COMMIT; -- 会话2此时插入成功 COMMIT;
- 会话1(事务A):开启事务,统计符合条件的行数
- 适用场景:极少使用,仅适用于对数据一致性要求极高、并发量极低的场景(比如金融核心交易的关键步骤)。
三、四大隔离级别对比表
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 并发性能 | 适用场景 |
|---|---|---|---|---|---|
| READ UNCOMMITTED | ✅ | ✅ | ✅ | 最高 | 几乎不用 |
| READ COMMITTED | ❌ | ✅ | ✅ | 较高 | 普通查询、非核心业务 |
| REPEATABLE READ | ❌ | ❌ | ❌ | 中等 | 绝大多数业务(MySQL 默认) |
| SERIALIZABLE | ❌ | ❌ | ❌ | 最低 | 金融核心交易、高一致性场景 |
注:✅ 表示会出现该问题,❌ 表示不会出现。