存储主控故障后的数据丢失和修复的过程,重看Oracle五大日常的进程先后顺序,回顾进程间的逻辑关系,对照着五大进程读写和操作系统OS文件系统关系。查阅 Oracle 19c 官方手册,对照配套存储故障 ORA-00600 报错深度回顾学习。Commit之后会发生什么,什么时候会有不一致的可能
0. 故障点:从存储主控故障说起
在之前两篇实战案例中(ORA-00600 kdsgrp1-qetlbr 块损坏108-ORA-00600 kdsgrp1-qetlbr 内部错误排查与 RMAN 块级恢复-CSDN博客 和 ORA-00600 13011 表索引不一致)109-ORA-00600 13011 修复实录:存储迁移后 AWR 表索引一致性恢复-CSDN博客,我们遇到的核心问题都是:存储双控制器物理故障后,数据库出现了逻辑层面的数据不一致。
为什么存储故障会引发 "逻辑不一致"?为什么 RMAN 全文件验证没有报物理坏块,但 ORA-00600 却持续报出?答案藏在 Oracle 的后台进程工作机制里 ------commit 成功只代表日志落盘,不代表数据块落盘;存储故障时,内存中的脏块与磁盘上的数据块之间,可能存在 Oracle 自己都无法察觉的 "半同步" 状态。
日志概念:
Oracle 采用WAL(Write-Ahead Logging,先写日志后写数据,日志写先行)
持久性模型。COMMIT 返回成功,只代表 LGWR 把事务 redo 写入了 online redo log。此时数据块可能还在 Buffer Cache 里,脏块可能还没写入数据文件,表段、索引段在磁盘上的内容可能仍是旧版本。
The atomic write of the redo entry containing the transaction's commit record is the single event that determines that the transaction has committed.
------ Oracle Database Concepts, Chapter 14
1. Oracle存储层次:块 → 区 → 段 → 表空间 → 数据文件
理解进程之前,必须先搞清楚 Oracle 存储的两层结构:逻辑结构(Oracle 自己认和操作系统无关)和 物理结构(操作系统认)。
TypeScript
表空间 Tablespace(逻辑容器)
└── 段 Segment(表段 / 索引段 / Undo 段 / 临时段)
└── 区 Extent(一组逻辑连续的数据块)
└── 数据块 Data Block(最小 I/O 单位,8KB / 16KB)
└── 映射到 OS 块 → 存放在 数据文件 Data File
|------------|------------------------------|---------|
| 层级 | 含义 | 管理者 |
| Data Block | Oracle 最小逻辑 I/O 单位,默认 8KB | Oracle |
| Extent | 一次分配的、逻辑连续的数据块集合 | Oracle |
| Segment | 一个数据库对象占用的所有 extent(表段、索引段等) | Oracle |
| Tablespace | 逻辑容器,段属于表空间 | Oracle |
| Data File | 操作系统文件,真正存字节 | OS |
理解概念:
操作系统只认文件和 OS 块;段、区、Oracle 数据块都是 Oracle 自己的概念。一个段的不同 extent 可以分布在同一表空间的不同数据文件中。这就是为什么存储迁移后,段级别的逻辑映射可能断裂 ------ 物理文件复制过去了,但 Oracle 内部的块地址映射关系没有被正确重放。
2. 五大核心进程详解
2.1 LGWR --- Log Writer(日志写进程)
官方定义:The log writer process (LGWR) writes the log buffer to the online redo log. LGWR writes redo entries from the SGA redo log buffer to the online redo log file.
核心职责:把 SGA 中 Redo Log Buffer 的内容写入 Online Redo Log File。它是事务持久性的唯一保证者。
触发时机:
- 用户提交事务(COMMIT)------ 最关键的触发点
- 每 3 秒定期刷新(无论是否提交)
- Redo Log Buffer 1/3 满 或 达到 1MB
- DBWn 要写脏块,但对应 redo 还没落盘(Write-Ahead 协议要求)
- 日志切换(Log Switch)时
TypeScript
COMMIT
├─ 分配 SCN(系统更改号)
├─ LGWR:事务 redo + commit record + SCN → online redo log
├─ LGWR 返回完成
└─ Oracle 给客户端返回 "Commit complete"
-- 数据块写没写数据文件?此处不管。
-- 这就是 fast commit mechanism:
-- 数据块修改可以推迟,只要 redo 先持久化,崩溃后就能重放。
与存储结构的关系:
LGWR 不认识段,也不认识表。它只处理 redo 条目 ------redo 条目里记录的是:哪个文件、哪个块、哪个字节改了(前镜像 / 后镜像向量)。所以 LGWR 是 "物理变更日志写手",不是 "数据写手"。
2.2 DBWn --- Database Writer(数据库写进程)
官方定义 : The database writer process (DBWn) writes the contents of dirty buffers in the database buffer cache to data files. Before DBWn can write a modified buffer, all redo records associated with the changes to the buffer must be written to disk (the write-ahead protocol).
核心职责 :把 Buffer Cache 中的 Dirty Buffer(脏块)写入数据文件中的对应数据块。
DBWn 可以有多个(DB_WRITER_PROCESSES 参数控制),从 DBW0 到 DBW9、DBWa 到 DBWj。
触发时机:
- Buffer Cache 中没有足够的干净可复用块
- 增量检查点推进
- 日志切换(Log Switch)
- 表空间 OFFLINE / READ ONLY / TRUNCATE / BEGIN BACKUP
- 每 3 秒定期唤醒
日志协议(WAL):
TypeScript
DBWn 想写脏块 → 数据文件
│
├─ 检查:这个块对应的 redo 是否已在 online redo log?
│
├─ 没有 → 喊 LGWR 先写 redo
├─ LGWR 写完返回
└─ DBWn 才把脏块写进数据文件
与存储结构的关系:
DBWn 写的是某个 data block,这个 block 属于某个 segment(表段 / 索引段 /undo 段),这个 segment 属于某个 tablespace,最终落在某个 data file。但 DBWn 不按 "表" 写,也不按 "事务" 写------ 它是按 Buffer / 检查点 / LRU 批量写。所以看到的现象是:commit 很久之后,具体的表中的新行才会出现在数据文件里。这是逻辑设计如此。
2.3 CKPT --- Checkpoint(检查点进程)
官方定义 : The checkpoint process (CKPT) updates the control file and data file headers with checkpoint information. CKPT does not write data blocks to data files or redo blocks to online redo log files.
核心职责:
- 发信号让 DBWn 写脏块
- 更新控制文件中的检查点信息
- 更新数据文件头中的检查点 SCN 和时间戳
- 记录 online redo log 中的恢复起点位置
检查点是什么:从这个 SCN 开始,之前所有脏块要么已经写进数据文件,要么崩溃恢复时不需要再从更早的 redo 开始重放。
增量检查点 :现代 Oracle 不是 "全量刷盘",而是 增量检查点:DBWn 定期把最老的脏块写掉,CKPT 把检查点位置往前推。崩溃恢复范围 = 最后一个检查点 SCN → 最新 redo。
sql
SELECT target_mttr, estimated_mttr FROM v$instance_recovery;

与存储结构的关系:
CKPT 不碰段、不碰区。它改的是控制文件(数据库级元数据)和数据文件头(文件级元数据)。但它决定了:表段 / 索引段在崩溃后,要从哪一段 redo 开始重放。
2.4 SMON --- System Monitor(系统监控进程)
官方定义 : The system monitor process (SMON) performs instance recovery after a failed instance. SMON is also responsible for cleaning up temporary segments that are no longer in use and recovering dead transactions skipped during crash recovery.
核心职责:实例恢复:
- 读控制文件 / 数据文件头 ------ 找到最后一个检查点 SCN
- 前滚(Roll Forward) ------ 从 online redo log 重放所有已提交事务的 redo,把数据块恢复到崩溃前的状态
- 打开数据库 ------ 此时用户可以连接,但未提交事务的脏块还在内存中
- 回滚(Roll Back) ------ 用 Undo 段把未提交事务改过的块还原
其他日常工作:
- 表空间 / 数据文件重新 ONLINE 后,补做之前跳过的终止事务
- 清理建索引失败留下的临时段
- 字典管理表空间中合并相邻空闲区(ASSM 下已基本用不到)
与存储故障案例的关联:
存储故障后,SMON 在实例恢复时重放 redo。如果 redo 日志或数据文件因为存储故障出现了 "块级错位"(物理上文件完整,但 Oracle 内部块地址映射不对),SMON 重放出来的数据就会出现逻辑不一致 ------ 这正是 ORA-00600 \kdsgrp1 和 \13011 的根因。
2.5 PMON --- Process Monitor(进程监控进程)
官方定义 : The process monitor process (PMON) cleans up failed user processes and frees resources that the failed processes were using. PMON rolls back uncommitted transactions, releases locks, and deallocates other SGA resources.
触发场景:
- 服务器进程被 OOM Kill
- 网络断开,客户端异常掉线
- ALTER SYSTEM KILL SESSION
- 调度进程(Dispatcher)挂了
执行动作: - 重置事务表(Transaction Table)
- 释放行锁 / 表锁
- 回收 PGA 相关资源
- 从活跃进程列表移除死进程
- 12c 后:PMON 检测到死进程,由 CLMN / CLnn 并行清理
与存储结构的关系:
PMON 不直接写数据文件。它只标记 "这个事务活动状态",真正把未提交修改滚掉,依赖 Undo 段和后续的延迟回滚。
PMON 管 "进程kill掉的状态但是资源不能漏",不管 "数据块长啥样"。
4. 全景时间线顺序:一次 DML 的完整生命周期
TypeScript
事务开始
│
├─ 服务器进程读数据块到 Buffer Cache(数据文件 → SGA)
├─ 修改 Buffer Cache 中的 data block(表段 / 索引段变脏数据)
├─ 写 Redo Log Buffer(记录变更向量)
│
COMMIT
│
├─ LGWR:redo + commit SCN → online redo log #事务持久化点
├─ 客户端收到 "Commit complete"
├─ 数据块可能还在内存,数据文件还是旧的
│
检查点推进 / Buffer 不够 / 日志切换
│
├─ CKPT:更新控制文件 + 数据文件头
├─ CKPT 通知 DBWn
├─ DBWn:先确认 redo 已落盘 → 写 dirty block → data file
│
实例崩溃(存储故障 / 断电)
│
├─ SMON:从上次 Checkpoint SCN 开始重放 redo
├─ 已提交事务:补进数据块(前滚)
├─ 未提交事务:用 Undo 回滚(后滚)
│
会话异常断开
└─ PMON:释放锁、回滚未提交事务、回收 PGA 资源
5. 从进程视角理解:存储故障为什么会导致逻辑不一致
回到最开始的问题:存储双控制器故障后,为什么 RMAN 验证不到物理坏块,但 ORA-00600 却持续报出?
时刻 1:DML 执行中
用户 UPDATE 一张表,数据块已在 Buffer Cache 中被修改,Redo 已写入 Online Redo Log(LGWR 完成)。但 DBWn 还没来得及把脏块写进数据文件。
时刻 2:存储双控制器同时故障
存储阵列突然断电。数据文件中的旧块完整无损(物理上没坏),但 Online Redo Log 中记录的 "新块变更" 可能还没来得及全部重放到数据文件。SMON 恢复时会重放这些 redo------ 理论上应该恢复一致。
5. 从进程视角理解:存储故障为什么会导致逻辑不一致
回到最开始的问题:存储双控制器故障后,为什么 RMAN 验证不到物理坏块,但 ORA-00600 却持续报出?
时间点1:DML 执行中
用户 UPDATE 一张表,数据块已在 Buffer Cache 中被修改,Redo 已写入 Online Redo Log(LGWR 完成)。但 DBWn 还没来得及把脏块写进数据文件。
时间点2:存储双控制器同时故障
存储阵列突然断电。数据文件中的旧块完整无损(物理上没坏),但 Online Redo Log 中记录的 "新块变更" 可能还没来得及全部重放到数据文件。SMON 恢复时会重放这些 redo------ 理论上应该恢复一致。
时间点3:数据卷迁移到新存储
物理文件被复制到新存储。文件字节层面是完整的(所以 RMAN VALIDATE 报 OK),但 Oracle 内部的块地址映射、事务槽状态、SCN 计数器等元数据,可能因为 "半写" 状态而出现错位。
时间点4:业务运行,触发 ORA-00600
当某个 SQL 通过索引访问表行时,发现索引指向的 ROWID 在表块中找不到对应行(13011),或者 KDS 层在组装行片段时发现块内部结构异常(kdsgrp1)。这些都是 "逻辑不一致"------ 文件物理完好,但 Oracle 自己的内部映射乱了。
物理文件被复制到新存储。文件字节层面是完整的(所以 RMAN VALIDATE 报 OK),但 Oracle 内部的块地址映射、事务槽状态、SCN 计数器等元数据,可能因为 "半写" 状态而出现错位。
时间点5:业务运行,触发 ORA-00600
当某个 SQL 通过索引访问表行时,发现索引指向的 ROWID 在表块中找不到对应行(13011),或者 KDS 层在组装行片段时发现块内部结构异常(kdsgrp1)。这些都是 "逻辑不一致"------ 文件物理完好,但 Oracle 自己的内部映射乱了。
核心概念:
存储故障后的 "逻辑不一致",本质是WAL 机制下内存态与磁盘态的时序差被故障放大了。物理坏块可以靠 RMAN 备份恢复,但逻辑映射断裂需要靠ALTER TABLE MOVE(重建段)+REBUILD INDEX(重建索引)来彻底修复 ------ 这就是我们在之前案例中采用的方案。
6. 发生错误时,查询路径
|----------------------------|-----------|---------------------------------------------------|
| 现象 | 进程等待 | 应该先查看 |
| COMMIT 慢、log file sync 等待高 | DBWn | LGWR / Redo 磁盘写性能 / LGWR 并行度 |
| db file parallel write 等待高 | LGWR | DBWn 数量 / 存储随机写性能 / 脏块生成速度 |
| checkpoint completed 等待高 | 加内存 | 日志切换频率 / MTTR 目标设置 / DBWn 写入速度 |
| 长事务回滚慢 | PMON | Undo 段大小 / SMON 恢复进度 / 大事务拆分 |
| 会话卡死、锁不释放 | 杀会话 | PMON 是否跟上清理 / 死锁检测 / 阻塞链 |
| 崩溃后实例恢复慢 | 加 DBWn 数量 | 检查点频率 / Redo 量 / FAST\_START\_MTTR\_TARGET |
| AWR 快照不生成、清理失败 | MMON | WRM\$ 系列表状态 / SYSAUX 表空间 / ORA-00600 \13011 |
7. TIPS
日常我们在SQL中常见commit,将事务收尾把表数据写进磁盘,背后的原理是 "commit 先把变更日志写进磁盘,数据块以后再写,崩溃了用日志补"。
|--------|------------------------|
| 进程 | 职责 |
| LGWR | 保提交:redo 落盘 = 事务持久化 |
| DBWn | 写数据块:脏块批量写入数据文件 |
| CKPT | 记恢复起点:控制文件 + 数据文件头 SCN |
| SMON | 实例级救场:前滚 + 后滚恢复 |
| PMON | 会话级善后:释放锁 + 回滚死事务 |
结合之前的存储故障案例:这五大进程的工作时序,再次理解为什么 物理文件完好 ≠ 逻辑数据一致------WAL 机制下的内存 - 磁盘时序差,在存储突发故障时会被放大为块级、段级的逻辑错位,这也正是 ORA-00600 系列错误的深层根因。