网站Logo 90的blog

Mysql事务隔离级别

root
1
2025-09-28

事务经常和 commitrollback 一起出现。但真正让我觉得事务重要的,是开始理解“多个请求同时操作数据库”之后。

例如,一个订单系统里:

  • 用户 A 正在修改订单;

  • 用户 B 正在查看同一笔订单;

  • 用户 C 又准备取消它。

如果没有合适的规则,不同请求可能会读到不一致的数据。MySQL 的事务隔离级别,就是用来规定并发事务之间“能看见彼此多少变化”的机制。

事务的四个基本特性

事务通常用 ACID 来概括:

  • 原子性(Atomicity):一组操作要么全部成功,要么全部失败。

  • 一致性(Consistency):事务执行前后,数据仍满足业务规则。

  • 隔离性(Isolation):并发事务之间不会随意互相干扰。

  • 持久性(Durability):事务提交后,数据不会因为程序崩溃轻易丢失。

隔离性。

并发事务可能出现的三个问题

为了方便理解,假设有一张账户表:

CREATE TABLE accounts (
    id INT PRIMARY KEY,
    balance DECIMAL(10, 2)
);

账户余额初始为 100。

脏读:读到了还没提交的数据

事务 A 修改余额,但还没有提交:

-- 事务 A
UPDATE accounts
SET balance = 50
WHERE id = 1;

这时事务 B 读到了余额 50:

-- 事务 B
SELECT balance FROM accounts WHERE id = 1;

随后事务 A 发现操作有误,执行回滚:

ROLLBACK;

余额又变回 100。

事务 B 刚才读到的 50 从来没有真正生效,这就是脏读。

不可重复读:同一个事务读两次,结果不同

事务 A 第一次查询余额:

-- 事务 A
SELECT balance FROM accounts WHERE id = 1;
-- 结果:100

这时事务 B 修改并提交:

-- 事务 B
UPDATE accounts
SET balance = 80
WHERE id = 1;

COMMIT;

事务 A 再次查询:

SELECT balance FROM accounts WHERE id = 1;
-- 结果:80

同一事务、同一条查询,结果却变了,这叫不可重复读。

幻读:同一个条件查询,出现了“新行”

事务 A 查询所有金额大于 100 的订单:

SELECT * FROM orders WHERE amount > 100;

事务 B 插入一条符合条件的订单并提交:

INSERT INTO orders (id, amount)
VALUES (101, 200);

COMMIT;

事务 A 再次执行同样的查询,发现多出一条记录。

这类“像凭空多出来一行”的现象,叫做幻读。

MySQL 的四种隔离级别

从低到高,MySQL 定义了四种标准隔离级别:

隔离级别

脏读

不可重复读

幻读

并发能力

READ UNCOMMITTED(读未提交)

可能

可能

可能

READ COMMITTED(读已提交)

不会

可能

可能

较高

REPEATABLE READ(可重复读)

不会

不会

理论上可能

较高

SERIALIZABLE(串行化)

不会

不会

不会

隔离越严格,数据越稳定;但锁等待和并发冲突通常也会更多。

1. READ UNCOMMITTED:读未提交

这是最低的隔离级别。一个事务可以读到其他事务尚未提交的修改。

它可能导致脏读,因此实际业务中很少使用。

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

除非是对一致性要求极低的场景,否则不建议选择它。

2. READ COMMITTED:读已提交

在这个级别下,只能读到其他事务已经提交的数据,因此避免了脏读。

但同一个事务里执行两次查询,若中间有其他事务提交修改,结果可能不同,也就是仍然会出现不可重复读。

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

很多数据库默认使用这个级别。它适合读写比较频繁、能接受“下一次查询看到最新已提交数据”的业务。

3. REPEATABLE READ:可重复读

这是 InnoDB 常见的默认隔离级别。

在一个事务中,多次执行普通查询,通常会看到事务开始时的一致性视图。因此可以避免不可重复读。

SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

例如事务开始后第一次读到余额 100,即使其他事务后来修改并提交了余额,当前事务再次普通查询,仍可能看到 100。

这里有一个容易混淆的点:MySQL 的 InnoDB 通过 MVCC(多版本并发控制)和 next-key lock 等机制,对幻读的处理比标准定义更复杂。

简单理解即可:

  • 普通 SELECT 通常是快照读,读取事务一致性视图;

  • SELECT ... FOR UPDATEUPDATEDELETE 等属于当前读,会读取较新的已提交版本,并可能使用锁防止其他事务插入符合条件的新记录。

因此,不能只背“可重复读会出现幻读”,还要结合具体 SQL 类型和是否加锁来看。

4. SERIALIZABLE:串行化

这是最严格的隔离级别。数据库尽量让并发事务表现得像一个接一个串行执行。

SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

它能避免脏读、不可重复读和幻读,但代价是并发能力下降,锁等待和死锁风险也会增加。

它适合对一致性要求极高、并发量相对有限的场景,但不适合不加评估地用于高并发业务。

一个常见误区:隔离级别不能解决所有并发问题

即使使用了可重复读,也不代表业务一定安全。

例如库存初始为 1,两个事务都先查询库存:

SELECT stock FROM products WHERE id = 1;

它们都读到库存为 1,于是都认为可以下单。如果后续分别扣减,仍然可能出现超卖或更新丢失等问题。

更可靠的做法是把“判断库存”和“扣减库存”放在一条 SQL 中:

UPDATE products
SET stock = stock - 1
WHERE id = 1
  AND stock > 0;

然后检查受影响行数:

  • 影响 1 行:扣减成功;

  • 影响 0 行:库存不足,或商品不存在。

这比“先查库存,再更新库存”更安全。必要时还可以配合事务、行锁、版本号或分布式锁。

如何查看和设置隔离级别

查看当前会话隔离级别:

SELECT @@transaction_isolation;

设置当前会话:

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

注意,隔离级别最好在开启事务之前设置:

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

START TRANSACTION;

-- 执行业务 SQL

COMMIT;

应用开发中,也要确认数据库连接池会不会复用连接。某个请求修改了 session 级别配置后,如果连接回到池中,后续请求可能继承这个设置。因此通常更推荐统一在数据库或应用初始化阶段配置,而不是随意在业务代码中修改。

总结

隔离级别决定了一个事务在并发环境中,何时能看见其他事务提交的数据,以及数据库为了保证这种规则需要付出多少并发代价。

实际开发中,最常见的关注点是:

  • READ COMMITTED:每次查询看到最新已提交的数据;

  • REPEATABLE READ:同一事务中的普通查询保持较稳定的快照;

  • 关键业务不要只依赖隔离级别;

  • 库存、余额、抢购等场景要通过条件更新、事务和锁保证最终正确性。

理解隔离级别的价值,不只是为了记住四个英文名称,而是在遇到并发数据问题时,知道该从“事务、SQL 写法、锁和业务约束”几个方向一起排查。

动物装饰