数据库幻读(Phantom Read)是数据库并发事务控制中可能发生的一种现象,它属于**不可重复读(Non-repeatable Read)**的一个特例,但关注点不同。
核心定义
幻读发生在同一个事务内,当两次执行完全相同的查询语句时,后一次查询看到了前一次查询没有看到的行(通常是因为另一个并发事务在此期间插入了新记录或删除了满足条件的记录)。
关键特征
- 同一个事务内:发生在同一个事务的上下文。
- 相同的查询条件:两次执行的 SQL 查询语句(特别是
WHERE子句)完全一样。 - 结果集发生变化:第二次查询返回的结果集行数发生了变化(新增了之前没有的行,或者之前存在的行消失了)。
- 由 INSERT 或 DELETE 引起:主要是由其他并发事务插入新记录或删除符合查询条件的记录造成的。
- 范围查询:通常发生在执行范围查询(如
WHERE status = 'active',WHERE price > 100)的时候。单行查询(WHERE id = 1)不会发生幻读(会发生不可重复读)。
举例说明
假设有一个 orders 表,包含 id 和 status 字段。初始状态有一条记录 (id: 1, status: 'pending')。
-
事务 A 开始:
START TRANSACTION; SELECT * FROM orders WHERE status = 'pending'; -- 返回 id=1 的记录 -
事务 B 开始并提交:
START TRANSACTION; INSERT INTO orders (id, status) VALUES (2, 'pending'); -- 插入一条新记录 COMMIT; -- 提交插入操作 -
事务 A 再次执行相同查询:
SELECT * FROM orders WHERE status = 'pending'; -- 现在返回 id=1 和 id=2 两条记录! COMMIT;
问题出现了! 事务 A 在同一个事务内,两次执行完全相同的查询 SELECT * FROM orders WHERE status = 'pending',第一次得到 1 条记录,第二次却得到了 2 条记录。这条多出来的 id=2 的记录就像一个“幻影”一样(Phantom)突然出现了。这就是幻读。
幻读 vs 不可重复读
- 不可重复读(Non-repeatable Read):同一个事务内,两次读取同一行数据,内容发生了变化(其他事务 UPDATE 了该行并提交)。焦点在数据内容的改变。
- 幻读(Phantom Read):同一个事务内,两次执行相同的范围查询,结果集的数量发生了变化(新增或删除了满足条件的行)。焦点在结果集行数的增减(新增或删除“幻影行”)。
如何避免幻读?
幻读的避免依赖于数据库的**事务隔离级别(Transaction Isolation Level)**和具体的实现机制:
- 可串行化(SERIALIZABLE):这是 SQL 标准规定的最高隔离级别。它通过强制事务串行执行(或使用严格的锁机制,如范围锁/间隙锁)来彻底避免幻读。代价是并发性能最低。
- 可重复读(REPEATABLE READ):
- SQL标准定义:此级别仅要求防止不可重复读,并不要求防止幻读。
- 具体数据库实现:
- MySQL (InnoDB引擎):通过在 REPEATABLE READ 级别使用间隙锁(Next-Key Locking) 锁住索引记录之间的“间隙”,阻止其他事务在查询涉及的范围内插入新记录,从而有效防止幻读。这是 MySQL 的一个重要特性。
- PostgreSQL, Oracle 等:在标准的 REPEATABLE READ 级别下,通常不能保证阻止幻读。要防止幻读,需要显式将隔离级别设置为 SERIALIZABLE,或者使用显式锁(如
SELECT ... FOR UPDATE)。
总结幻读的关键点
| 特性 | 描述 |
|---|---|
| 本质 | 同一事务内相同查询的结果集行数不一致 |
| 主要原因 | 其他事务插入或删除了符合本次查询条件的行 |
| 隔离级别 | SERIALIZABLE 级别可彻底解决;MySQL REPEATABLE READ 通过间隙锁也能解决 |
| 与不可重复读区别 | 幻读关注结果集行数变化(INSERT/DELETE),不可重复读关注数据内容变化(UPDATE) |
简单来说,幻读就是你在同一个事务里反复查看同一份名单(查询条件),却发现名单上的人变多了或者变少了(有人悄悄加入或退出),而这份变化是由其他事务造成的。解决它需要数据库提供更强的隔离性保障(通常是 SERIALIZABLE 或 MySQL REPEATABLE READ)。
519

被折叠的 条评论
为什么被折叠?



