首页 / 资讯中心 / 文章详情

备库ORA-01578/ORA-26040:NOLOGGING引发的坏块修复指南

备库ORA-01578/ORA-26040:NOLOGGING引发的坏块修复指南 ★ FEATURED ARTICLE
1. 错误成因拆解从NOLOGGING到备库落差的完整链路1.1 两个ORA错误的真实含义凌晨两点四十手机监控弹出一条短信备库数据库不可用。登录备库查看告警日志ORA-01578和ORA-26040堆在一起而主库一切正常。第一反应是磁盘坏了急匆匆跑机房、看存储、扫坏道。折腾到天快亮才把目光落到主库白天的那条INSERT /* APPEND */上——原来这根本不是介质故障而是NOLOGGING操作埋下的“定时炸弹”。先来把这两个错误弄清楚。ORA-01578是Oracle数据块损坏的经典错误码通常伴随ORA-01110提示具体的文件编号和块编号。如果是在一个健康的物理备库上突然出现这个错很多人的第一反应就是“盘坏了”。但真正具有指向性的错误是紧随其后的ORA-26040Block was read using ROWID but the block is nologged。这句话翻译过来就是“你读到的这个数据块在重做日志里压根没有记录”。也就是说备库上的数据块处于一种“理论上不该存在”的未记录状态。这两个错误组合出现基本能锁定一个事实主库曾经对这个块执行过NOLOGGING操作由于物理备库完全依赖redo日志进行数据同步主库没有为这些块生成redo备库自然接收不到内容。备库在应用日志或对外提供查询时一旦扫到这些块就会触发ORA-01578而Oracle内部在做块合法性检查时发现这个块被标记为NOLOGGING于是追加报出ORA-26040。理解这个因果链后面所有修复动作才有的放矢。1.2 NOLOGGING操作在Data Guard下的传播机制NOLOGGING操作本质上是一种“绕开redo”的加速写入方式。比如直接路径INSERT、CREATE TABLE AS SELECT、索引REBUILD这些操作在NOLOGGING模式下会直接把数据块写入数据文件跳过redo日志生成。在没有Data Guard的普通环境中这个设计几乎无感因为数据确实写到了磁盘上主库自己读起来完全正常。可一旦有了物理备库问题立刻暴露。物理备库的工作原理很纯粹主库生成redo备库拿到redo后原样应用以此实现数据一致。主库执行NOLOGGING操作后数据块已经在主库的数据文件里但redo日志里没有对应记录。备库的恢复进程根本无从得知这批块的内容。如果备库恰好处于活动备库状态对外提供查询某个会话扫到这些块时会发现块的内容与Oracle的日志链对不上ORA-26040就在那一刻爆出来。打个比方主库在账本上记了一笔“仓库已发货”但没有把运单号同步给物流系统备库拿着物流系统的底账去核对自然对不上账。生活化地理解就是主库认为数据已经写入成功备库却什么都不知道。只要后续没有新的redo覆盖这些块这个问题就会一直存在而且会随着备库的查询和恢复操作反复报错。1.3 典型触发场景哪些操作最容易惹祸我整理了一下操作类型具体SQL/场景是否产生redo在备库的表现直接路径加载INSERT /* APPEND */ INTO ... SELECT ...不产生ADG查询时报ORA-26040建表CREATE TABLE ... AS SELECT ... NOLOGGING不产生备库恢复/查询时报ORA-01578索引重建ALTER INDEX ... REBUILD NOLOGGING不产生扫描索引时报ORA-26040分区操作ALTER TABLE ... SPLIT PARTITION / EXCHANGE PARTITION视模式而定分区扫描时报错LOB类操作LOB的DIRECT WRITE场景不产生读取LOB时报错数据加载工具SQL*Loader的DIRECTTRUE且设置UNRECOVERABLE不产生对应表/分区报错其中最常见的是DBA或开发人员在赶工时图快用NOLOGGING模式做了大批量数据加载或索引重建事后又忘了把对象的日志属性改回来。我遇到过的情况是某开发在测试环境建表时习惯了加NOLOGGING结果建表脚本一不小心带到了生产环境。主库当时没有任何异常直到备库有查询触碰到新表才开始告警。还有一点必须提醒主库数据本身通常是完整且可读的问题只出在备库视角。所以主库一切正常备库狂报坏块这种情况在DG环境里几乎可以认定为NOLOGGING闯的祸不需要急着怀疑存储硬件。2. 修复前必须做好的三件事实况确认与影响评估2.1 从错误信息定位文件和块看到ORA-01578时先冷静不要跳进“换磁盘”的误区。第一步要做的是把错误上下文完整捞出来。告警日志里通常会打印类似这样的信息ORA-01578: ORACLE data block corrupted (file # 7, block # 2334) ORA-01110: data file 7: /u01/oradata/DB/datafile/users01.dbf ORA-26040: Block was read using ROWID but the block is nologged这三个错误码要连起来看。ORA-01110告诉我们文件路径ORA-01578告诉我们块号ORA-26040则提示了这很可能不是物理坏道而是“逻辑性”错误。下一步我建议直接查询备库的v$database_block_corruption视图它会把Oracle自动检测到的可疑块列出来SELECT file#, block#, corruption_type, block_size FROM v$database_block_corruption ORDER BY file#, block#;如果输出的corruption_type为NOLOGGING事情就清楚了。这个视图非常关键它会帮助你在进入下一步之前快速掌握损坏块的总量级和分布范围。如果只有寥寥几块修复起来相对轻松如果一口气报了上千块就要提前评估重做数据量。我在第一次处理这种问题时就吃了亏看到ORA-01578就直接让存储工程师做磁盘健康检测白白浪费了三个小时。其实只要多看一眼错误码后面的ORA-26040就能省下大量排查时间。2.2 反查主库对象与受影响数据范围拿到file#和block#之后下一个动作是在主库上反查出这些块到底属于哪个对象。这一步的意义在于修复策略完全取决于对象类型和大小。定位对象时用dba_extents视图做个交集判断就行SELECT e.segment_name, e.segment_type, e.partition_name, e.tablespace_name, e.extent_id FROM dba_extents e WHERE e.file_id file_id AND block_id BETWEEN e.block_id AND e.block_id e.blocks - 1;这个SQL能精确告诉我们某一块属于哪个段。实际操作中可能存在多种对象类型混在一起的情况比如索引、分区表、LOB段。如果是分区表还要进一步确认损坏块落在哪个分区如果是LOB段需要额外关联dba_lobs来定位是哪个表字段的LOB。这里有一个细节值得注意备库的file#和主库的file#在Data Guard环境下通常是一致的因为数据文件编号由控制文件定义且会同步。如果你不放心建议用dba_data_files把两个库的表空间和file#对照一下再执行查询。还有一个常被忽略的地方——如果受影响块跨越了多个extent说明问题可能不是一个段而是多个段甚至是整个表空间级别的直接路径写入。2.3 快速判断当前风险等级很多人在备库报ORA-26040时第一反应是“赶紧在主库做点什么”。但正确的动作恰恰相反先评估当前状态再行动。风险等级需要从两个维度判断。第一个维度是错误出现的场景如果只是ADG对外查询时某个会话报错说明redo应用进程并没有中断主库业务不受影响备库只是部分数据不可用但如果告警日志显示MRPManaged Recovery Process中断备库已经停止应用日志那么DR保护能力已经缺失必须优先把日志应用恢复起来否则主备差距会越拉越大。第二个维度是受影响范围通过v$database_block_corruption的数行数基本可以判断是单点问题还是大范围问题。结合主库反查结果如果对象是小表小索引修复成本很低如果是几百GB的大表或者跨越多个文件的大量块就需要选择不同的修复策略。我在实际操作中会在这一步列一个简单的评估表单受影响文件数量、受影响对象类型、对象大小、备库当前能否正常应用日志、主库是否有可用的备份。这五项信息几乎决定了后面所有方案的取舍。先把这些信息抓齐再谈怎么修。3. 三种主流修复路径与选型权衡3.1 方案A从主库端重写受影响段理解了问题本质之后最顺理成章的修复思路就是让这些NOLOGGING块在主库上被新的redo覆盖备库通过日志应用自然补齐内容。具体做法是回到主库对受影响段做一次物理级重写。最常见的手法包括ALTER TABLE T_TRADE_2024 MOVE; ALTER INDEX IDX_T_TRADE_2024 REBUILD;如果你处理的是分区表只需重建受影响的分区ALTER TABLE T_TRADE_2024 MOVE PARTITION P_2024;涉及LOB对象时需要额外指定LOB存储子句ALTER TABLE T_TRADE_2024 MOVE LOB(PAYLOAD) STORE AS (TABLESPACE USERS);为什么要用MOVE而不是普通UPDATE因为普通DML只会修改有变化的行无法保证覆盖所有被NOLOGGING写入的块。而MOVE会把整张表的数据重新写入一遍过程中的所有块修改都会进入redo日志备库应用这批日志后原先空洞的块内容就会被完整填补。REBUILD索引同理。这个方案的优点显而易见操作路径短不需要动备库不需要额外备份恢复现场。缺点是对超大对象不友好MOVE需要额外的表空间容量且操作过程中表会短暂不可用。在动手之前务必确认目标表空间有足够剩余空间并评估业务是否可以接受这段维护窗口。3.2 方案B使用备份恢复受影响数据文件如果受影响对象巨大比如几百GB的核心业务表在主库上跑一次MOVE可能耗费数小时业务完全不可接受。此时可以考虑备份恢复的路线。思路是用主库的RMAN备份先恢复备库的受影响文件再利用归档日志让备库追赶主库。但这条方案的实际操作细节远比听起来复杂。关键在于Data Guard环境下的SCN一致性备库控制文件中记录的信息与备份中数据文件的SCN必须匹配否则恢复链路会断掉。多数情况下更稳妥的方式是直接对备库执行RMAN恢复RMAN STARTUP MOUNT; RMAN RESTORE DATAFILE 7; RMAN RECOVER DATAFILE 7;前提是备库有对应的备份集且备份链路保持完整。实际生产环境中很多备份策略并不会单独给备库做备份而是通过主库备份再复制过去。如果你的环境没有现成的备库备份这条路径会变得相当困难。即便如此我仍然推荐在思路里保留这个选项因为它的恢复时间通常比重建整个备库要短。还有个容易踩的坑手工恢复备库数据文件时如果主库有RMAN备份且备份后日志尚未清理可以用RMAN的catalog机制找到正确的incarnation。但如果你对incarnation切换不熟搞不好会让备库脱轨得更远。这一点在后面的常见问题里展开细说。3.3 方案C重建备库当问题范围大、涉及文件多、或者备库已经被各种尝试性修复搞得状态混乱时重建备库反而是最可控的选择。重建物理备库的标准姿势是用RMAN的duplicate命令RMAN DUPLICATE TARGET DATABASE FOR STANDBY FROM ACTIVE DATABASE;如果你不想从活动库直接复制也可以先做主库备份再将备份传输到备库恢复最后创建standby控制文件并注册归档日志。这个方案的优点是彻底干净。重建过程不受原有损坏块影响备库所有数据文件都是新恢复的日志链路也从零开始。缺点同样突出耗时取决于数据量大小重建期间备库完全不可用等于DR保护出现空窗期。用我自己的经验判断什么时候必须选C当v$database_block_corruption视图里躺着几千个块、分布跨越多个数据文件、且反查对象五花八门时逐段重写几乎不可行备份恢复的数据文件也可能同样包含NOLOGGING块这时候重建备库才是止损最快的方式。3.4 方案对比一览表对比维度方案A主库段重写方案B备份恢复文件方案C重建备库操作复杂度低中高中恢复时长取决于对象大小取决于备份恢复速度取决于数据量和网络是否影响主库有轻微维护窗口基本不影响不直接操作主库是否影响备库可用性备库可保持运行备库需挂起恢复备库完全不可用适合场景中小对象、分区单文件/大对象大规模多对象、状态混乱做技术选型时我习惯于先按这个顺序过滤能用方案A解决的不升级单文件、大对象看备份条件选B多对象、范围不清或无备份保障时直接跳C。宁可花几小时重建备库也不要在一个不可控的恢复链路上反复折腾。4. 实战过程一次典型修复操作实录4.1 场景设定与应急处置为了把操作流程讲清楚我用一个发生在某生产库的真实场景做蓝本。环境是Oracle 12c R2配置了物理备库备库开启了ADG对外提供查询。某天主库有人执行了一批CTAS NOLOGGING操作重建了几张大表次日备库告警v$database_block_corruption中记录了数百个损坏块分布在3个数据文件中。定位后发现受影响的主对象是三张核心业务表以及这几张表上的若干索引。每张表约80GB属于不上不下的大小——重建备库有点浪费主库MOVE又需要不少时间。我当时第一个动作不是急着修复而是取消了备库的日志应用ALTER DATABASE RECOVER MANAGED STANDBY DATABASE CANCEL;这样做的原因是错误块已经存在如果继续让MRP在错误块上反复碰撞不仅会反复刷告警日志还可能把恢复进程搞到一个更别扭的状态。暂停应用后redo日志会继续堆积在备库端不会丢失只是暂不应用给我留出充足的处置时间。这一步在操作前要先确认主备之间的归档日志传输链路正常否则会出现日志断层。如果备库上还有其他依赖这些表的查询业务此时要提前通知应用侧停掉相关报表查询。虽然查询报错不会影响备库数据文件的物理状态但大量查询在超时重试时会加重备库负担干扰后续修复动作。4.2 在主库完成对象重写确认备库暂停应用后我在主库开始处理。先对最核心的表T_TRADE_2024检查它的日志属性SELECT table_name, logging, tablespace_name FROM dba_tables WHERE table_name T_TRADE_2024;检查结果确认该表的LOGGING属性为NO这正是NOLOGGING CTAS建表时留下的痕迹。我的处理顺序是先把表的日志属性改回来再执行MOVE操作ALTER TABLE T_TRADE_2024 LOGGING; ALTER TABLE T_TRADE_2024 MOVE;MOVE执行完后主库会生成这一整张表所有数据块的redo记录备库在后续应用这些redo时就能把NOLOGGING空洞完全覆盖。对于索引我用REBUILD来重建ALTER INDEX IDX_T_TRADE_2024 REBUILD;在等待MOVE执行期间我做了几件辅助事务。一是检查v$archived_log确认主库redo日志同步传输到备库二是观察MOVE作业的进度视图三是对可能出现空间压力的表空间做了一次扩容评估。特别是80GB的表做MOVE需要额外80GB的空间来安置新拷贝这块如果不提前准备MOVE会在表空间满时直接失败。这里必须说一个很容易被忽视的动作MOVE操作本身如果在NOLOGGING模式下执行等于白折腾。有些DBA在操作时习惯性沿用对象的已有属性结果表还是NOLOGGINGMOVE依然不产生redo。动手前建议显式跟上LOGGINGALTER TABLE T_TRADE_2024 MOVE LOGGING;这个写法能保证无论表之前是什么属性都以完整日志模式进行移动。三张表都处理完后我再次在主库确认所有受影响对象的LOGGING属性已经变成YES索引也一样。这一步做完意味着主库侧已经产生了足够的redo日志备库只要把这些日志应用上去问题就解决了。4.3 恢复日志应用并验证主库侧处理完成后回到备库恢复日志应用ALTER DATABASE RECOVER MANAGED STANDBY DATABASE DISCONNECT FROM SESSION;恢复命令发出后我习惯先等一两分钟然后检查MRP的状态SELECT process, status, client_process, sequence# FROM v$managed_standby;正常情况下MRP的状态会显示为APPLYING_LOG。接下来重点观察主备之间的日志应用差距SELECT dest_id, applied_scn, current_scn FROM v$archive_dest_status WHERE database_mode PHYSICAL STANDBY;当applied_scn和current_scn逐渐接近并最终一致时说明备库已经追上了主库最新状态。这时候我在备库上直接查询之前报错的三张表验证是否已经恢复正常SELECT COUNT(*) FROM T_TRADE_2024;查询能顺利返回说明MOVE产生的redo已经成功覆盖了NOLOGGING块。随后我再检查v$database_block_corruption坏块记录已经消失就是修复完成的标志。严格来说v$database_block_corruption不是一只“内存刷新”的普通视图它基于坏块记录维护当块被有效redo覆盖后该记录会在一段时间内自动清理。如果还有残留可以重启备库实例确认一次但大多数情况下不需要。验证没有问题时我还会跑一次RMAN的物理校验来给自己吃一颗定心丸RMAN BACKUP VALIDATE CHECK LOGICAL DATAFILE 7, 8, 9;这个命令会逐块读取数据文件并做逻辑校验确认没有残留的坏块。4.4 失败回滚与应急预案任何变更都要考虑失败怎么办。我在执行主库MOVE前记录了主库的当前SCN和备库的当前应用SCN-- 主库执行 SELECT current_scn FROM v$database; -- 备库执行 SELECT applied_scn FROM v$archive_dest_status;万一MOVE过程中主库操作失败或者备库日志应用被新错误打断至少能判断出备库与主库的差距大小。如果差距较小可以解决新错误后继续追赶如果差距极大且无法定位新错误原因就要考虑备库重建。还有一层更稳妥的兜底方案是在动手前确认主库存在有效的RMAN全量备份。真到万不得已时能用备份恢复备库文件而不是被迫回到原点重建。实际操作中我曾遇到过主库里根本没有可靠备份导致备库故障后无法快速恢复的情况那是很被动的局面。所以这条预案无论方案怎么选都要在动手前确认清楚。5. 事后加固让NOLOGGING在Data Guard环境中不再乘虚而入5.1 在主库开启强制日志与审核问题修复只是第一步防止问题再次发生才是关键。最有效的防线是在主库开启强制日志ALTER DATABASE FORCE LOGGING;开启之后即使某人仍然执行了NOLOGGING操作Oracle也会自动把它转换为完整日志模式redo照常生成。这就让数据加载的“省日志”效果被削弱但换来了备库的数据安全。在Data Guard环境下这笔买卖绝对划算。强制日志的代价是性能损耗和归档日志量上升。既然主备都建了DR保护是第一优先级。我在环境里普遍建议开启除非有充分的性能测试数据证明无法接受额外开销。如果实在要临时关闭做超大数据加载必须走变更审批流程并且在操作完成后立即重新开启ALTER DATABASE NOFORCE LOGGING; -- 超大数据加载记得用 LOGGING 模式 -- 完成后马上恢复 ALTER DATABASE FORCE LOGGING;同时还需要审查变更脚本里是否有人为加NOLOGGING关键字。SQL*Loader加载作业里的UNRECOVERABLE关键字也要一并排查这些都建议在标准调度脚本中统一改为RECOVERABLE。这个阶段我还建议对现有对象做一次日志属性体检把库里所有处于NOLOGGING状态的表和索引找出来SELECT owner, table_name, tablespace_name FROM dba_tables WHERE logging NO AND owner NOT IN (SYS,SYSTEM); SELECT owner, index_name, tablespace_name FROM dba_indexes WHERE logging NO AND owner NOT IN (SYS,SYSTEM);查出结果后与开发沟通确认是否误加对非必要对象一律改回LOGGING属性。5.2 建立NOLOGGING变更监控脚本数据库本身没有一个现成视图能直接告诉你“哪张表最近做了NOLOGGING操作”。这意味着与其依赖事后发现不如把监控前置。我通常建议的做法是分三层第一层定期巡查备库的v$database_block_corruption只要出现新记录就告警。第二层在主库审计层面开启对DDL语句的审计特别关注CREATE TABLE、ALTER TABLE、ALTER INDEX等语句中是否包含NOLOGGING关键字。第三层在备库的ADG查询巡检中定期用RMAN的VALIDATE命令检查最关键的业务表空间。每次执行大批量数据加载或索引维护后运维负责人都应该确认两件事该作业是否使用了NOLOGGING关键字主库force logging状态是否开启。把这两条作为变更复核的必选项比任何事后监控都直接。5.3 把经验沉淀到运维手册与演练这类故障非常典型完全值得写进运维手册和灾备演练场景里。我的建议是把“主库NOLOGGING引发备库坏块”作为固定演练科目标准SOP按五步执行取消备库日志应用、查询v$database_block_corruption、反查主库对象、按对象大小选择修复方案、恢复日志应用并验证。灾备演练不只是走流程更要验证一个关键能力备库从异常状态下追日志的能力。实际演练中才能发现某些备份链路不可用、某些归档日志有断层、某些表空间没有多余空间容纳MOVE临时数据。这些问题真的出故障时才暴露代价就太大了。演练的目的不是证明流程存在而是确保流程在任何情况下都能跑得通。我个人在多次处理这个问题后的体会是遇到备库ORA-01578和ORA-26040不要先怀疑硬件先怀疑NOLOGGING。修复的最终动作往往不在备库而在主库——让主库产生有效的redo去覆盖问题块。只要记住这一点处理节奏就顺畅多了。如果你维护的是Data Guard环境建议现在就打开强制日志并把这类场景写进应急预案这样才能少熬夜、少折腾。
阅读完成 · 觉得有帮助?
咨询建站