ANSI SQL 隔离级别中的 幻读(Phantom Read) 是指在同一事务内,由于其他事务插入或删除了符合当前事务查询条件的数据行,导致同一查询多次执行时返回不同的结果集。幻读的核心是 结果集的行数发生变化(新增或消失的行),而不仅仅是同一行数据的值被修改。
幻读的本质
- 针对范围查询:幻读通常发生在范围查询(如
WHERE age > 20
)或全表扫描时。 - 新增或删除的行:其他事务插入或删除的行如果符合当前事务的查询条件,会导致当前事务后续查询的结果集出现“幻觉”。
- 与不可重复读的区别:
- 不可重复读:同一行数据的值被其他事务修改(如
UPDATE
)。 - 幻读:结果集中新增或删除了行(如
INSERT
或DELETE
)。
- 不可重复读:同一行数据的值被其他事务修改(如
幻读是否仅针对插入?
不完全正确。虽然幻读最常见的原因是其他事务插入新行,但以下两种操作也可能导致幻读:
- 插入(INSERT):新增符合查询条件的行。
- 删除(DELETE):删除符合查询条件的行。
- 更新(UPDATE):在某些场景下,更新操作可能间接导致幻读(见下文)。
示例分析
场景 1:插入新行导致幻读
-- 事务A(隔离级别:可重复读)
BEGIN TRANSACTION;
SELECT * FROM users WHERE age > 25; -- 返回 2 条记录
-- 事务B 插入一条 age=30 的新记录并提交
INSERT INTO users (id, name, age) VALUES (3, 'Charlie', 30);
COMMIT;
-- 事务A 再次查询
SELECT * FROM users WHERE age > 25; -- 返回 3 条记录(出现幻读)
COMMIT;
场景 2:更新导致幻读(特殊情况)
如果其他事务通过 UPDATE
操作修改数据,使得某些行从“不符合条件”变为“符合条件”,也可能导致幻读:
-- 初始数据
INSERT INTO users (id, name, age) VALUES (4, 'David', 20);
-- 事务A(隔离级别:可重复读)
BEGIN TRANSACTION;
SELECT * FROM users WHERE age > 25; -- 返回 2 条记录
-- 事务B 更新 David 的年龄为 30 并提交
UPDATE users SET age = 30 WHERE id = 4;
COMMIT;
-- 事务A 再次查询
SELECT * FROM users WHERE age > 25; -- 可能返回 3 条记录(出现幻读)
COMMIT;
为什么更新可能导致幻读?
- 在 可重复读(Repeatable Read) 隔离级别下,ANSI SQL 标准允许幻读,但某些数据库(如 MySQL)通过 MVCC(多版本并发控制) 或 锁机制 规避了部分幻读。
- 如果事务A的查询基于快照(如 MySQL 的 RR 级别),则不会看到事务B提交的更新后的数据,因此不会出现幻读。
- 如果事务A的查询是“当前读”(如
SELECT ... FOR UPDATE
),则可能看到事务B提交的更新后的数据,导致幻读。
总结
- 幻读主要针对插入和删除,但更新操作也可能间接导致幻读(如数据从“不可见”变为“可见”)。
- ANSI SQL 标准 中,可重复读隔离级别允许幻读,而串行化(Serializable)隔离级别通过锁机制彻底避免幻读。
- 实际数据库的实现差异:
- MySQL 的可重复读(通过 MVCC)可以避免大部分幻读。
- PostgreSQL 的可重复读严格遵循 ANSI 标准,允许幻读,需升级到串行化隔离级别解决。
关键结论
- 幻读不仅是插入导致,删除和更新(间接)也可能引发。
- 是否发生幻读取决于 隔离级别的具体实现(如快照隔离、锁机制等)。