幻读与脏读的区别
幻读与脏读是数据库事务并发控制中的两种不同现象,它们的核心区别在于:脏读读的是“未提交”的修改数据,而幻读读的是“新增或删除”的行记录。
为了帮你清晰区分,我们从三个维度来剖析:
1. 核心定义(本质区别)
- 脏读(Dirty Read):一个事务读取了另一个事务尚未提交(随后可能回滚)的数据。如果读取后该事务回滚,读到的数据就是无效的“脏数据”。
- 幻读(Phantom Read):一个事务在前后两次执行同一个查询时,返回的结果集行数不同(变多或变少)。这是因为另一个事务在这期间**插入(INSERT)或删除(DELETE)**了符合查询条件的新数据。
2. 影响的数据范围(操作类型)
- 脏读:针对现有数据行的修改(UPDATE)。重点在于数据的“值”是否被确认。
- 幻读:针对数据集合的新增(INSERT)或删除(DELETE)。重点在于数据“条数”或“集合边界”发生了变化。
3. 对应的隔离级别(SQL标准)
- 脏读:发生在
READ UNCOMMITTED(读未提交)级别。任何高于该级别的隔离级别(如 READ COMMITTED)都能防止脏读。 - 幻读:发生在
REPEATABLE READ(可重复读)级别(在 MySQL/InnoDB 中除外,其 RR 可防止幻读,但标准 SQL 中允许幻读)。只有在SERIALIZABLE(串行化)级别下才能完全防止幻读。
举例说明(时间线对比)
假设有一个 账户表,初始数据为:用户A:余额 100元。
场景一:脏读(读到了未提交的修改)
| 时间 | 事务1(转账) | 事务2(查账) |
|---|---|---|
| T1 | 开启事务,将A余额改为 0元(未提交) | |
| T2 | 开启事务,查询A余额,读到 0元(脏读) | |
| T3 | 发生异常,执行 ROLLBACK(回滚),余额恢复为100元 | |
| T4 | 基于读到的0元继续业务逻辑(数据错误) |
后果:事务2读到了根本不存在的数据(0元),因为事务1最终回滚了。
场景二:幻读(读到了新增/删除的行)
| 时间 | 事务1(统计) | 事务2(插入) |
|---|---|---|
| T1 | 开启事务,统计年龄>18岁的用户,结果为 10人 | |
| T2 | 开启事务,插入1个新的成年用户,并 COMMIT(提交) | |
| T3 | 再次执行同一条统计SQL,结果变为 11人(幻读) | |
| T4 | 事务1发现前后数据条数不一致,产生困惑 |
后果:事务1在同一事务内,两次读取的结果集集合发生了变化,仿佛出现了“幻觉”。
一句话总结口诀
- 脏读:读到了别人还没定下来的修改(会滚回去的)。
- 幻读:读到了别人新加进来的记录(或者少了记录),导致数量对不上了。
特别提示(针对 MySQL):如果你使用 MySQL 的 InnoDB 引擎,在 REPEATABLE READ 默认隔离级别下,InnoDB 通过 MVCC(多版本并发控制)和间隙锁(Gap Lock)解决了幻读问题,但标准 SQL 定义中幻读是允许在该级别下发生的,这一点在面试或学习理论时需要注意区分。