花拾录
← 返回知识库

数据库事务隔离级别用例子说话:脏读、幻读到底长什么样

数据库AI2026/09/240 阅读0 评论

事务隔离级别是数据库面试和日常开发中的高频话题,但很多人只是背下了“读未提交、读已提交、可重复读、串行化”这四个名词,对脏读、不可重复读、幻读的具体表现却一知半解。本文用两个典型例子,把抽象概念变成看得见的现象。

准备工作:一张账户表

CREATE TABLE account (
  id INT PRIMARY KEY,
  balance INT NOT NULL
);
INSERT INTO account VALUES (1, 1000);

以下例子基于 MySQL InnoDB 的默认设置。

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

脏读指一个事务读到了另一个事务尚未提交的修改。

时刻事务A事务B
T1BEGIN;
T2UPDATE account SET balance = 2000 WHERE id = 1;
T3BEGIN;
T4SELECT balance FROM account WHERE id = 1; → 读到 2000
T5ROLLBACK;
T6再查一次,余额变回 1000

事务B在T4读到的2000,是事务A最终回滚掉的“脏数据”。如果业务根据这个值做了扣款或发货,就会出错。

如何避免:把隔离级别设为**读已提交(Read Committed)**或更高。MySQL 中通过 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; 设置。

幻读:同一范围查询,行数变了

幻读指同一事务内,两次执行相同的范围查询,第二次却多出(或少了)一些行。

时刻事务A事务B
T1BEGIN;
T2SELECT * FROM account WHERE balance > 500; → 1 行
T3BEGIN;
T4INSERT INTO account VALUES (2, 800);
T5COMMIT;
T6再次同样查询 → 变成 2 行

事务A两次查询条件完全相同,结果集却“凭空”多了一行,这就是幻读。它和不可重复读的区别在于:不可重复读针对同一行的值被修改,幻读针对结果集的行数发生变化。

MySQL InnoDB 的实际情况:在可重复读(Repeatable Read)级别下,InnoDB 通过 MVCC(多版本并发控制) 让普通 SELECT 始终读取事务开始时的快照,因此上面的例子在 RR 下不会出现幻读——第二次查询仍返回 1 行。

但注意:如果事务A执行的是 SELECT ... FOR UPDATE 这类当前读,InnoDB 会使用 Next-Key Lock(间隙锁) 阻止事务B插入,同样能避免幻读。所以“RR 完全解决幻读”这个说法并不严谨,准确说法是:InnoDB 在 RR 级别下通过 MVCC 和间隙锁,在大多数场景中避免了幻读。

四种隔离级别速查

隔离级别脏读不可重复读幻读
读未提交可能可能可能
读已提交不可能可能可能
可重复读不可能不可能InnoDB 下基本避免
串行化不可能不可能不可能

实践建议

  1. 互联网业务常用读已提交(RC):减少间隙锁带来的死锁,提升并发。
  2. 金融类对一致性要求高的场景可用可重复读(RR),但要理解间隙锁可能增加锁冲突。
  3. 不要依赖默认值:不同数据库默认级别不同(Oracle、PostgreSQL 默认 RC,MySQL 默认 RR),跨库迁移时要显式确认。
  4. 业务层加锁(如 SELECT ... FOR UPDATE)比单纯调隔离级别更可控,但要注意锁的范围和顺序。

理解这些现象,比背诵名词更重要——下次遇到“数据对不上”的 bug,你就能快速定位是不是隔离级别在捣鬼。

评论(0)

  • 还没有评论,来抢沙发~

相关文章