Skip to content

Oracle 事务与锁

Oracle 的事务与锁要围绕 Undo、Redo、SCN、一致性读和行锁一起理解。

Undo 让事务能回滚和构造旧版本,Redo 让提交能恢复,SCN 标识数据库一致性时间点,锁解决并发写冲突。

学习目标

学完这一页,你要能说明:

  1. Oracle 一致性读为什么依赖 SCN 和 Undo。
  2. Undo、Redo、SCN 分别解决什么问题。
  3. 一条 update 从加锁、写 Undo、写 Redo 到 commit 的全过程。
  4. TX 锁、TM 锁、行锁等待、死锁分别是什么。
  5. snapshot too old 为什么出现,怎么处理。
  6. 商业系统里如何缩短事务、避免锁等待和 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 会怎样:

  1. 更新后无法回滚。
  2. 普通查询无法看到一致时间点。
  3. 读写更容易互相阻塞。
  4. 长查询遇到并发更新时无法构造旧版本。

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';

过程:

  1. 通过唯一索引或扫描定位行。
  2. 检查 status = 'IDLE'
  3. 对目标行加 TX 行锁。
  4. 生成 Undo,保存旧状态。
  5. 修改 Buffer Cache 里的数据块。
  6. 生成 Redo 记录变化。
  7. commit 时 LGWR 刷 Redo。
  8. 生成提交 SCN。
  9. 锁释放,其他事务可见。

如果两个事务同时改同一行:

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 synccommit 等 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,等待

处理原则:

  1. 多资源更新按固定顺序。
  2. 缩短事务。
  3. 不在事务中调用远程接口。
  4. 条件命中索引,避免长时间扫描。
  5. 捕获死锁错误后可做有限重试,但要先修根因。

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$sessionv$lockv$sql,结合 AWR/ASH 判断等待事件。

常见坑

后果正确做法
小事务频繁 commitlog 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 已被覆盖。

追问:

  1. Undo 和 Redo 有什么区别?
  2. Oracle 一致性读为什么需要 SCN?
  3. 为什么 commit 不等于刷数据块?
  4. snapshot too old 怎么排查和优化?

关联知识点

知识点说明
Oracle 从零到生产级掌握主学习线
Undo、Redo 与 SCN 原理更深入的事务和恢复机制
核心全过程原理SQL、内存、事务、排查全链路
索引与优化器索引、ROWID、执行计划
性能排查等待事件、锁和 AWR/ASH