Oracle 的读一致性是怎么实现的?UNDO 有什么作用?
简化版
Oracle 通过多版本读一致性保证查询看到一致的数据快照。查询开始后,如果某些数据块已经被其他事务修改,Oracle 可以利用 UNDO 中保存的旧版本信息构造出查询时刻的数据。UNDO 既用于一致性读,也用于事务回滚和数据库恢复。
详细版
Oracle 的读一致性核心是:读操作通常不阻塞写操作,写操作通常也不阻塞普通读操作。查询会看到某个时间点的一致视图,而不是边读边看到别人提交的新旧混合结果。
UNDO 的主要作用包括:
- 保存数据修改前的旧值,支持事务回滚;
- 为一致性读构造旧版本数据;
- 崩溃恢复时帮助撤销未提交事务;
- 支持闪回查询等能力。
例如一个长查询开始后,另一个事务修改并提交了某些行。长查询继续读取时,如果需要看到查询开始时的旧值,Oracle 会根据 UNDO 还原出旧版本。若旧版本所需 UNDO 已被覆盖,就可能出现 ORA-01555: snapshot too old。
完整版教学
一、读一致性解决什么问题
没有读一致性时,一个查询可能前半部分读到旧数据,后半部分读到别人刚提交的新数据。对于统计、报表、分页、批量导出,这会造成结果不一致。
Oracle 的一致性读让一条查询看到“同一个时间点”的数据视图。这样即使查询过程中有其他事务提交,当前查询也不会被这些变化打乱。
这就是 Oracle 面试中经常问“读不阻塞写、写不阻塞读”的原因。但这里说的是普通一致性读,不包括显式加锁读。
二、UNDO 保存的是修改前的数据
当事务修改一行时,Oracle 会在 UNDO 中保存修改前的信息。这样如果事务回滚,数据库可以把数据恢复到修改前状态。
例如:
UPDATE account SET balance = balance - 100 WHERE id = 1;
Oracle 会保留这次更新前的余额信息。如果事务执行:
ROLLBACK;
就能利用 UNDO 把余额恢复。
三、UNDO 如何支持一致性读
假设查询 Q 在 10:00:00 开始,事务 T 在 10:00:05 修改并提交了某行。Q 在 10:00:10 扫描到这行时,磁盘块里可能已经是新值。
但 Q 需要看到 10:00:00 的快照,所以 Oracle 会沿着数据块里的事务信息找到相关 UNDO,把这行还原成查询开始时可见的旧版本。
这个过程不是复制整张表,而是按需要构造一致版本。
四、为什么会出现 snapshot too old
如果一个查询运行很久,而系统又有大量更新,旧版本需要的 UNDO 可能已经被复用覆盖。此时 Oracle 无法再构造查询开始时的旧版本,就可能报:
ORA-01555: snapshot too old
常见原因包括:
- 长查询运行时间太久;
- UNDO 表空间太小;
- 更新量太大;
- 提交过于频繁导致旧版本很快被覆盖;
- UNDO 保留策略不足以覆盖长查询需求。
解决思路通常是优化长查询、增大 UNDO 表空间、合理设置 UNDO 保留时间、降低不必要的大批量更新压力。
五、UNDO 和 REDO 的区别
很多人会把 UNDO 和 REDO 混在一起。可以这样区分:
| 类型 | 关注点 | 作用 |
|---|---|---|
| UNDO | 修改前的数据 | 回滚、一致性读、撤销未提交事务 |
| REDO | 修改过程的重做记录 | 崩溃恢复、保证已提交修改可重放 |
UNDO 让数据库“能退回去”,REDO 让数据库“能重做回来”。Oracle 的事务可靠性和一致性,两者都离不开。
六、常见误区与追问
| 机制 | 保存/使用什么 | 主要用途 |
|---|---|---|
| UNDO | 修改前的数据 | 回滚、一致性读、撤销未提交事务 |
| REDO | 修改的重做记录 | 崩溃恢复、重放已提交修改 |
| 一致性读 | 查询开始时快照 | 普通读不被新提交打乱 |
记忆钩子:UNDO 让 Oracle “回到旧版本”,REDO 让 Oracle “重做已提交修改”。读一致性靠的是按需用 UNDO 构造旧版本。
用时间线理解 ORA-01555:查询 Q 10:00 开始扫描 1 亿行,事务 T 在 10:05 到 10:20 持续更新同一批表块并提交;Q 到 10:30 才读到这些块,需要 10:00 的旧版本,但对应 UNDO 已被新事务复用覆盖,就可能报 snapshot too old。这个错误不是“查询读错了”,而是旧快照所需版本已经不可构造。
- 误区:Oracle 普通查询会等写事务释放锁。 普通一致性读通常利用 UNDO 看旧版本,不需要等正在提交后的新值。
- 误区:UNDO 只用于回滚。 UNDO 还支持一致性读、闪回查询和恢复中撤销未提交事务。
- 误区:snapshot too old 是表坏了。 它通常表示长查询需要的旧版本 UNDO 已被覆盖,和 UNDO 保留、更新压力、查询耗时有关。
- 追问:一致性读和当前读有什么区别? 一致性读看快照版本;加锁读或 DML 更关注当前可修改版本和锁状态。
- 追问:UNDO 表空间太小会怎样? 旧版本更容易被覆盖,长查询和闪回能力受影响,也可能增加 ORA-01555 风险。
- 追问:UNDO 和 REDO 如何配合恢复? REDO 重放已提交修改,UNDO 撤销未提交修改,二者共同保证崩溃后事务一致性。
七、加强记忆
Oracle 读一致性的主线是:查询要看一个稳定快照,数据被别人改了也没关系,Oracle 可以用 UNDO 构造旧版本。UNDO 记住修改前的数据,服务回滚、一致性读和恢复;长查询需要旧版本但 UNDO 不够时,就可能遇到 snapshot too old。