ABMR 仅修复物理坏块(Physical Block Corruption),绝不修复逻辑坏块(Logical Block Corruption)。
本文档涵盖:
ABMR 是 Oracle Active Data Guard 提供的一项自动块级故障恢复机制。当主库或备库在运行时遇到 物理坏块(物理 I/O 读取失败,块校验和不匹配),ABMR 会自动从另一端的数据库(备库或主库)获取该块的完好副本,透明地覆盖损坏块,无需人工干预。
┌─────────────────────────────────────────┐
│ 客户端查询触发物理块 I/O 失败 │
│ ORA-01578: 文件 X 块 Y 读取失败 │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Oracle 内核检测到校验和错误或块格式错误 │
│ 校验: 块是否属于可自动修复的物理坏块 │
│ ✗ 逻辑坏块 → 不触发 ABMR,直接报错 │
│ ✓ 物理坏块 → 进入 ABMR 流程 │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ ABMR 通过网络从另一端数据库请求块的副本 │
│ │
│ 备库出错 → 从主库 FETCH 干净块 │
│ 主库出错 → 从备库 FETCH 干净块 │
└─────────────────┬───────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 接收到块副本后, 写入损坏位置, 刷新缓存 │
│ 记录日志: │
│ "Automatic block media recovery: │
│ successfully restored block Y from │
│ <source>" │
└─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ 客户端重新尝试读取 — 成功 │
│ 整个流程对应用完全透明 │
└─────────────────────────────────────────┘以下条件 必须同时满足,缺一不可。任何一个不满足,ABMR 都不会触发,且不会报明显错误(仅告警日志中记录静默跳过)。
READ ONLY WITH APPLYSELECT OPEN_MODE, DATABASE_ROLE FROM V$DATABASE;正确值 | 错误值 |
|---|---|
READ ONLY WITH APPLY | READ ONLY(无 Real-Time Apply) |
MOUNTED(备库未打开) | |
READ WRITE(这在备库不可能) |
⚠️ 常犯错误:仅设置了
READ ONLY而没有WITH APPLY,MRP 进程不运行,ABMR 初始化直接失败。
APPLYING_LOG 状态SELECT PROCESS,STATUS, CLIENT_PID, THREAD#, SEQUENCE#
FROM V$MANAGED_STANDBY
WHERE PROCESS LIKE'MRP%';正确状态 | 错误状态 |
|---|---|
APPLYING_LOG | IDLE(Redo 应用暂停) |
WAITING_FOR_LOG(无可用归档) | |
进程不存在(MRP 未启动) |
TRUESELECT*FROM V$OPTION WHERE PARAMETER ='Active Data Guard';正确值 | 错误值 |
|---|---|
TRUE | FALSE(无 ADG 许可,ABMR 永远不工作) |
这是 最容易被忽略的硬性条件。即使备库在运行,很多站点仅使用了 Oracle Data Guard(免费许可)而非 Active Data Guard(需要额外购买许可),ABMR 功能在内核层被禁用。
LOG_ARCHIVE_CONFIG 必须在主备两端都包含完整的 DG 名称-- 主库检查
SHOW PARAMETER log_archive_config;
-- 备库检查
SHOW PARAMETER log_archive_config;正确示例:
log_archive_config = 'DG_CONFIG=(orcl,orcl_stby)'
-- ↑ 主库 DB_UNIQUE_NAME ↑ 备库 DB_UNIQUE_NAME错误示例:
log_archive_config = 'DG_CONFIG=(orcl)' -- 漏了备库名称
log_archive_config = 'DG_CONFIG=(orcl_stby)' -- 漏了主库名称
log_archive_config = '' -- 空值如果主备任意一端漏掉了对方的
DB_UNIQUE_NAME,ABMR 的跨库块级拉取通道在初始化时就会失败。
这是 ABMR 主题下 最核心、最容易被误解 的概念。
特征 | 说明 |
|---|---|
定义 | 块在存储介质层面已损坏(磁盘扇区损坏、RAID 校验失败、存储缓存故障) |
检测方式 | Oracle 读取块后,计算校验和与块头中记录的校验和不匹配 |
表现 | ORA-01578: ORACLE data block corrupted (file # X, block # Y) |
V$DATABASE_BLOCK_CORRUPTION | CORRUPTION_TYPE = 'FRACTURED' 或 'CHECKSUM' |
根源 | 存储层问题,数据库内部结构本身无问题 |
ABMR 是否工作 | ✅ 是 — 从另一端拷贝完好副本即可修复 |
特征 | 说明 |
|---|---|
定义 | 块校验和与头格式都正确,但块内部数据逻辑结构已损坏(如索引条目指向不存在的数据行、事务槽(ITL)损坏、行目录溢出) |
检测方式 | ANALYZE TABLE ... VALIDATE STRUCTURE CASCADE、RMAN VALIDATE CHECK LOGICAL、DBVERIFY 加 logonly=yes 参数 |
表现 | ORA-01498: block check failure、查询返回不一致数据、索引查询报错 |
V$DATABASE_BLOCK_CORRUPTION | CORRUPTION_TYPE = 'LOGICAL' 或 'MEDIUM' |
根源 | Oracle 内部逻辑损坏(Bug、异常关闭、介质恢复时 Redo 损坏、人为操作) |
ABMR 是否工作 | ❌ 否 — 备库上的同一块也可能逻辑损坏(如果损坏来自 Redo 重放) |
比较维度 | 物理坏块 | 逻辑坏块 |
|---|---|---|
校验和 | ❌ 不匹配 | ✅ 匹配 |
块头格式 | ❌ 错误或不可读 | ✅ 正确 |
内部数据逻辑 | 未知 | ❌ 不一致/损坏 |
ABMR 能否修复 | ✅ 能 | ❌ 不能 |
需要 RMAN 备份 | 不需要(利用对端副本) | 需要 |
典型根因 | 磁盘坏道、RAID 故障、SSD 异常 | Oracle Bug、异常关闭、Redo 损坏 |
ABMR 的核心假设是:另一端数据库的同一数据块是完好的。
为什么?因为逻辑坏块的成因可能是:
在上述场景 #1 和 #2 中,即使 ABMR 从主库拉取块副本,拿到的也是 同样损坏的逻辑块,修复无意义。
Oracle 内核在决定是否触发 ABMR 时,会判断坏块的类型:
-- 通过告警日志验证 ABMR 是否尝试触发
-- 逻辑坏块:仅看到 ORA-01578,无 ABMR 相关日志
-- 物理坏块:ORA-01578 后紧跟 "Automatic block media recovery: requesting block..."这是 最硬、最可靠 的验证方法。不要只看参数设置,要实际模拟故障并观察日志。
-- 查看主库告警日志路径
SELECTVALUEFROM V$DIAG_INFO WHERE NAME ='Diag Alert';# tail 实时跟踪
tail -f$ORACLE_BASE/diag/rdbms/<db_name>/trace/alert_<sid>.log | grep-i"automatic block media recovery"看到以下输出 = ABMR 真正在工作:
Automatic block media recovery: requesting block 1234 from standby for file 5
Automatic block media recovery: successfully restored block 1234 from standby for file 5只看到以下内容 = ABMR 没有触发(逻辑坏块或条件不满足):
ORA-01578: ORACLE data block corrupted (file # 5, block # 1234)
ORA-01110: data file 5: '/u01/oradata/users01.dbf'后面没有 ABMR 恢复日志。
⚠️ 仅可在测试环境执行!不得在生产环境执行!
# 1. 确定数据文件路径和块号
# 从 V$DATABASE_BLOCK_CORRUPTION 或:
SELECT file_name, file_id FROM DBA_DATA_FILES WHERE tablespace_name ='USERS';
# 2. 在主库使用 dd 损坏一个物理块
# 假设 file_id=5, block_number=12345
# 块大小 8192 字节
dd if=/dev/zero of=/u01/oradata/users01.dbf bs=8192conv=notrunc seek=12344count=1
# ↑ seek = block_number - 1(dd 的 seek 是 0-based)
# 3. 立即查询该表
SELECT COUNT(*) FROM test_table; -- 预期:ORA-01578
# 4. 检查告警日志
tail -f$ORACLE_BASE/diag/rdbms/<db_name>/trace/alert_*.log | grep-i"automatic block"
# 5. 如果 ABMR 正常工作,会看到请求/恢复日志
# 验证坏块是否消失
SELECT * FROM V$DATABASE_BLOCK_CORRUPTION WHERE FILE# = 5 AND BLOCK# = 12345;
-- 应该无记录(已自动修复)SELECT FILE#,
BLOCK#,
BLOCKS,
CORRUPTION_TYPE,
CORRUPTION_CHANGE#,
RAC_ID
FROM V$DATABASE_BLOCK_CORRUPTION;CORRUPTION_TYPE | 含义 | ABMR 可修复? |
|---|---|---|
FRACTURED | 块内容断裂(物理) | ✅ |
CHECKSUM | 校验和不匹配(物理) | ✅ |
CORRUPT | 通用损坏(可能是物理或逻辑) | ❓ 需进一步分析 |
LOGICAL | 逻辑一致性损坏 | ❌ |
MEDIUM | 介质损坏但校验和正确(逻辑) | ❌ |
一旦 V$DATABASE_BLOCK_CORRUPTION 中确认损块类型为 LOGICAL 或 MEDIUM,ABMR 路径立即失效,应切换到以下方案之一:
# 在备库执行 BLOCKRECOVER(修复备库本地坏块)
RMAN> BLOCKRECOVER DATAFILE 5 BLOCK 12345;工作原理:
优点:不需要主库参与,不依赖主库块的完好性。
限制:
-- 1. 创建 Repair 表
BEGIN
DBMS_REPAIR.ADMIN_TABLES(
table_name =>'REPAIR_TABLE',
table_type => DBMS_REPAIR.REPAIR_TABLE,
action => DBMS_REPAIR.CREATE_ACTION,
tablespace_name =>'USERS'
);
END;
/
-- 2. 检查坏块
DECLARE
v_num_corrupt INT;
BEGIN
v_num_corrupt := DBMS_REPAIR.CHECK_OBJECT(
schema_name =>'SCOTT',
object_name =>'EMP',
repair_table =>'REPAIR_TABLE'
);
DBMS_OUTPUT.PUT_LINE('Corrupt blocks found: '|| v_num_corrupt);
END;
/
-- 3. 标记块为跳过(丢失该块数据,慎用!)
DECLARE
v_num_corrupt INT;
BEGIN
v_num_corrupt := DBMS_REPAIR.FIX_CORRUPT_BLOCKS(
schema_name =>'SCOTT',
object_name =>'EMP',
repair_table =>'REPAIR_TABLE'
);
DBMS_OUTPUT.PUT_LINE('Blocks fixed: '|| v_num_corrupt);
END;
/
-- 4. 跳过损坏块读取数据
SELECT/*+ SKIP_CORRUPT */*FROM SCOTT.EMP;⚠️ DBMS_REPAIR 是在丢失数据的前提下跳过坏块,执行前务必评估数据可丢失性。建议先抢救数据再标记。
如果逻辑坏块发生在 索引段(最常见的情况):
-- 通过 ANALYZE 或 V$DATABASE_BLOCK_CORRUPTION 确认
SELECT SEGMENT_NAME, SEGMENT_TYPE, PARTITION_NAME, TABLESPACE_NAME
FROM DBA_EXTENTS
WHERE FILE_ID =5
AND12345BETWEEN BLOCK_ID AND(BLOCK_ID + BLOCKS -1);
-- 如果段类型为 INDEX
DROPINDEX<owner>.<index_name>;
CREATEINDEX<owner>.<index_name>ON<table>(<columns>) ...;不需要修复坏块本身 — 删除重建索引即可得到无损坏的新索引结构。
对于表段逻辑损坏:
-- 1. 使用 ROWID 分段扫描定位可读记录
-- 先找出坏块对应的 ROWID 范围
-- 2. 跳过坏块 CREATE TABLE AS SELECT
CREATETABLE scott.emp_goodAS
SELECT/*+ SKIP_CORRUPT */*FROM scott.emp;
-- 3. 验证行数
SELECTCOUNT(*)FROM scott.emp_good;
SELECTCOUNT(*)FROM scott.emp; -- 可能比上面多或少(如果有坏块被跳过)
-- 4. 确认数据完整性后,替换原表
DROPTABLE scott.emp;
ALTERTABLE scott.emp_goodRENAMETO emp;方案 | 适用场景 | 数据丢失率 | 是否需要 RMAN 备份 | 复杂度 |
|---|---|---|---|---|
RMAN BLOCKRECOVER | 有可用备份 | 0% | ✅ 需要 | 低 |
DBMS_REPAIR | 无备份,非核心表 | 部分丢失(坏块内数据) | ❌ 不需要 | 中 |
索引重建 | 索引逻辑损坏 | 0%(数据在表中) | ❌ 不需要 | 低 |
CTAS 抢救 | 表段损坏 | 坏块内行丢失 | ❌ 不需要 | 中 |
从主库重建备库 | 大面积逻辑损坏 | 取决于恢复点 | ❌ 不需要 | 高 |
事实:ABMR 仅修复物理坏块。逻辑坏块(LOGICAL / MEDIUM 类型)永远不会触发 ABMR。
事实:ABMR 需要 Active Data Guard 许可(V$OPTION 返回 TRUE)。仅使用免费 Data Guard(物理备库处于 MOUNT 状态)不支持 ABMR。
READ ONLY 就够了事实:必须是 READ ONLY WITH APPLY。缺了 WITH APPLY 表示没有 Real-Time Apply,MRP 进程不运行,ABMR 初始化失败。
事实:四个条件缺一不可。最常见的问题是 LOG_ARCHIVE_CONFIG 中漏掉了一端的数据库唯一名称。
事实:逻辑坏块可能是因为 Redo 损坏或 Oracle Bug 导致,主库上的同一块也可能已损坏,或备库重放 Redo 后同样损坏。ABMR 不尝试逻辑坏块修复。
-- RMAN 物理与逻辑检测
RMAN> BACKUP VALIDATE CHECK LOGICAL DATABASE;
-- 或
RMAN> VALIDATE CHECK LOGICAL DATABASE;
-- DBVERIFY
$ dbv file=/u01/oradata/users01.dbf blocksize=8192 logonly=yes-- 查看 ABMR 相关的统计信息
SELECT NAME,VALUE
FROM V$SYSSTAT
WHERE NAME LIKE'%automatic block%';
-- Active Data Guard 统计
SELECT NAME,VALUE, TIME_COMPUTED
FROM V$ADG_STATS
WHERE NAME LIKE'%block%';建议加入日常巡检脚本:
-- ABMR 条件巡检脚本
COLUMNcondition FORMAT A50
COLUMNstatus FORMAT A20
SELECT'Open Mode'AScondition,
CASEWHEN OPEN_MODE ='READ ONLY WITH APPLY'THEN'✅ PASS'ELSE'❌ FAIL'ENDASstatus
FROM V$DATABASE
UNIONALL
SELECT'MRP0 Process',
CASEWHENEXISTS(SELECT1FROM V$MANAGED_STANDBY WHERE PROCESS ='MRP0'ANDSTATUS='APPLYING_LOG')
THEN'✅ PASS'ELSE'❌ FAIL'END
FROMDUAL
UNIONALL
SELECT'ADG License',
CASEWHEN(SELECTVALUEFROM V$OPTION WHERE PARAMETER ='Active Data Guard')='TRUE'
THEN'✅ PASS'ELSE'❌ FAIL'END
FROMDUAL
UNIONALL
SELECT'LOG_ARCHIVE_CONFIG',
CASEWHEN(SELECTVALUEFROM V$PARAMETER WHERE NAME ='log_archive_config')LIKE'%DG_CONFIG=%'
THEN'✅ CHECK (manual verify both sides)'ELSE'❌ FAIL'END
FROMDUAL;