Oracle 事务与锁
Oracle 的事务与锁要围绕 Undo、Redo、SCN、一致性读和行锁一起理解。
Undo 让事务能回滚和构造旧版本,Redo 让提交能恢复,SCN 标识数据库一致性时间点,锁解决并发写冲突。
学习目标
学完这一页,你要能说明:
- Oracle 一致性读为什么依赖 SCN 和 Undo。
- Undo、Redo、SCN 分别解决什么问题。
- 一条
update从加锁、写 Undo、写 Redo 到 commit 的全过程。 - TX 锁、TM 锁、行锁等待、死锁分别是什么。
snapshot too old为什么出现,怎么处理。- 商业系统里如何缩短事务、避免锁等待和 Undo/Redo 暴涨。
Oracle 事务主线
mermaid
flowchart TD
A["业务执行 DML"] --> B["定位数据行"]
B --> C["加行锁"]
C --> D["写 Undo 保存旧值"]
D --> E["修改 Buffer Cache 中数据块"]
E --> F["生成 Redo"]
F --> G["commit"]
G --> H["LGWR 刷 Redo"]
H --> I["生成提交 SCN"]
I --> J["返回成功"]Oracle 不要求 commit 时把所有数据块刷到数据文件。它要求提交相关 Redo 可靠落盘。因为只要 Redo 在,宕机后就能恢复已提交修改。
一致性读
Oracle 普通查询不会读未提交数据。查询开始时会确定一个查询 SCN,后续读取的数据要符合这个 SCN 的一致视图。
mermaid
flowchart TD
A["select 开始"] --> B["确定查询 SCN"]
B --> C["读取数据块"]
C --> D{"数据块中的行版本是否晚于查询 SCN"}
D -- "否" --> E["直接返回当前块中的版本"]
D -- "是" --> F["读取 Undo"]
F --> G["构造查询 SCN 时的旧版本"]
G --> H["返回一致结果"]这就是为什么 Oracle 普通读和写可以较好并发:读不一定等待写提交,而是通过 Undo 构造自己应该看到的版本。
Undo 的作用
| 作用 | 解释 | 示例 |
|---|---|---|
| 回滚 | 事务失败时恢复旧值 | update 后 rollback |
| 一致性读 | 查询需要旧版本时从 Undo 构造 | 长查询读取开始时的数据 |
| 闪回能力 | 部分 Flashback 功能依赖 Undo | 查询过去某个时间的数据 |
没有 Undo 会怎样:
- 更新后无法回滚。
- 普通查询无法看到一致时间点。
- 读写更容易互相阻塞。
- 长查询遇到并发更新时无法构造旧版本。
Redo 的作用
Redo 记录数据块变化,用于崩溃恢复。
mermaid
flowchart TD
A["修改 Buffer Cache"] --> B["生成 Redo 到 Redo Log Buffer"]
B --> C["commit 请求"]
C --> D["LGWR 写 redo log 文件"]
D --> E["commit 成功"]
E --> F["DBWR 后续刷脏块"]
F --> G["宕机后可用 Redo 恢复"]Redo 和 Undo 的区别:
| 机制 | 记录什么 | 用途 |
|---|---|---|
| Undo | 修改前的旧值 | 回滚、一致性读 |
| Redo | 修改产生的变化 | 崩溃恢复、介质恢复 |
| SCN | 一致性时间点 | 判断版本、恢复边界、提交顺序 |
SCN 是什么
SCN 可以理解为 Oracle 内部的逻辑时间戳,用来标识数据库的一致性位置。
mermaid
flowchart TD
A["事务开始"] --> B["执行 DML"]
B --> C["commit"]
C --> D["生成提交 SCN"]
D --> E["其他事务根据 SCN 判断可见性"]
D --> F["恢复过程根据 SCN 确定一致点"]你不需要把 SCN 理解成真实时间,它更像数据库内部单调推进的一致性编号。
update 和 commit 过程
sql
update medical_asset
set status = 'USED',
updated_at = sysdate
where asset_no = :asset_no
and status = 'IDLE';过程:
- 通过唯一索引或扫描定位行。
- 检查
status = 'IDLE'。 - 对目标行加 TX 行锁。
- 生成 Undo,保存旧状态。
- 修改 Buffer Cache 里的数据块。
- 生成 Redo 记录变化。
- commit 时 LGWR 刷 Redo。
- 生成提交 SCN。
- 锁释放,其他事务可见。
如果两个事务同时改同一行:
mermaid
sequenceDiagram
participant A as 事务A
participant B as 事务B
A->>A: update asset id=1
A->>A: 持有行锁未提交
B->>B: update asset id=1
B-->>A: 等待 A 提交或回滚
A->>A: commit
B->>B: 继续执行并重新判断条件常见锁
| 锁/等待 | 含义 | 常见原因 |
|---|---|---|
| TX 锁 | 行级事务锁 | 同一行被多个事务更新 |
| TM 锁 | 表级 DML 锁 | DML 与 DDL、外键等相关 |
enq: TX - row lock contention | 行锁等待 | 事务未提交、更新热点行 |
library cache lock/pin | 共享池对象竞争 | DDL、硬解析、对象失效 |
log file sync | commit 等 LGWR | 提交频繁、Redo IO 慢 |
Oracle 行锁通常不是“锁整张表”。但如果 SQL 条件没有索引,更新为了找到目标行可能扫描很多块,事务时间变长,锁持有时间也变长。
死锁
死锁来自互相等待。
mermaid
sequenceDiagram
participant A as 事务A
participant B as 事务B
A->>A: 更新资产1
B->>B: 更新资产2
A->>B: 再更新资产2,等待
B->>A: 再更新资产1,等待处理原则:
- 多资源更新按固定顺序。
- 缩短事务。
- 不在事务中调用远程接口。
- 条件命中索引,避免长时间扫描。
- 捕获死锁错误后可做有限重试,但要先修根因。
snapshot too old
ORA-01555: snapshot too old 是 Oracle 面试和生产高频问题。
mermaid
flowchart TD
A["长查询开始,拿到查询 SCN"] --> B["其他事务大量更新"]
B --> C["Undo 中保存旧版本"]
C --> D["长查询后续读到被改过的数据"]
D --> E["需要 Undo 构造旧版本"]
E --> F{"所需 Undo 还在吗"}
F -- "在" --> G["构造旧版本成功"]
F -- "不在" --> H["ORA-01555 snapshot too old"]解决方向:
| 方向 | 说明 |
|---|---|
| 优化长查询 | 降低查询耗时,减少需要旧版本的窗口 |
| 分批处理 | 不要一次扫完整大表 |
| 增加 Undo 空间和保留时间 | 让旧版本保留更久 |
| 错峰报表 | 避开高更新时段 |
| 使用只读库或数仓 | 报表不要压核心交易库 |
商业项目事务边界
| 场景 | 推荐做法 | 不这样会怎样 |
|---|---|---|
| 资产状态流转 | 条件更新 + 影响行数判断 | 后提交覆盖先提交 |
| 批量导入 | 分批提交、记录错误明细 | Undo/Redo 暴涨,锁时间长 |
| 支付/核心交易 | 本地事务内只做本库关键修改 | 远程调用拖长锁持有 |
| 报表统计 | 读库/数仓/物化结果 | 长查询影响 Undo 和缓存 |
| 审计流水 | 和主变更同事务写入 | 主数据和审计不一致 |
可运行 Demo:锁等待
会话 A:
sql
update medical_asset
set status = 'REPAIR'
where asset_no = 'A001';
-- 先不 commit会话 B:
sql
update medical_asset
set status = 'USED'
where asset_no = 'A001';会话 B 会等待 A。A 提交或回滚后,B 才能继续。
排查等待可以从会话、等待事件、阻塞对象入手。不同版本和权限下可用视图不同,常见方向是查看 v$session、v$lock、v$sql,结合 AWR/ASH 判断等待事件。
常见坑
| 坑 | 后果 | 正确做法 |
|---|---|---|
| 小事务频繁 commit | log file sync 压力 | 合理批量,减少无意义提交 |
| 一个事务更新几十万行 | Undo/Redo 暴涨,锁持有久 | 分批提交 |
| 报表扫核心交易表 | 影响缓存、Undo、一致性读 | 读库、数仓、物化视图 |
| 忽略索引导致更新扫描大表 | 事务耗时长,锁等待扩大 | 给更新条件建合适索引 |
| 认为 commit 会刷数据块 | 误解恢复机制 | commit 关键是 Redo 刷盘 |
| 只靠杀会话处理锁 | 问题反复出现 | 修事务边界和 SQL |
面试标准回答
text
Oracle 通过 Undo、Redo、SCN 和锁保证事务一致性。Undo 保存修改前的旧值,用于回滚和一致性读;Redo 记录数据块变化,用于崩溃恢复;SCN 是数据库内部一致性时间点。普通查询开始时确定查询 SCN,如果读到的数据版本晚于这个 SCN,就通过 Undo 构造旧版本。更新会定位行、加行锁、写 Undo、修改 Buffer Cache、生成 Redo;commit 时 LGWR 刷 Redo 并生成提交 SCN,数据块由 DBWR 后续写回。锁等待常见于多个事务更新同一行,snapshot too old 常见于长查询需要的 Undo 已被覆盖。追问:
- Undo 和 Redo 有什么区别?
- Oracle 一致性读为什么需要 SCN?
- 为什么 commit 不等于刷数据块?
snapshot too old怎么排查和优化?
关联知识点
| 知识点 | 说明 |
|---|---|
| Oracle 从零到生产级掌握 | 主学习线 |
| Undo、Redo 与 SCN 原理 | 更深入的事务和恢复机制 |
| 核心全过程原理 | SQL、内存、事务、排查全链路 |
| 索引与优化器 | 索引、ROWID、执行计划 |
| 性能排查 | 等待事件、锁和 AWR/ASH |
