【资源损坏类故障】:详细了解坏块
目录
1、物理坏块与逻辑坏块
1.1、物理坏块
1.2、逻辑坏块
2、两个坏块相关的参数
2.1、db_block_checksum
2.2、db_block_checking
3、检测坏块
3.1、告警日志
3.2、RMAN
3.3、ANALYZE
3.4、数据字典
3.5、DBVERIFY
4、修复坏块
4.1、RMAN修复
4.2、DBMS_REPAIR修复
4.3、BBED
5、写在最后
1、物理坏块与逻辑坏块
将数据库比喻成一本书,每一页看作是一个数据块。当我们看不懂书中某页的内容可能有3种原因:该页缺损、该页内容逻辑有错误、智商不够。我们的智商显然不是问题,这点首先排除。
所以书页缺损、书页上的内容逻辑错误是最可能的两点原因。其中页书缺损就好比数据库中的物理坏块,直接粗暴的破坏了这一页;书页上的内容逻辑错误就像是数据库中的逻辑坏块,写的内容驴头不对马嘴。
如果现在写的这篇文章让您读着不通顺或者读不懂,那这篇文章也属于是个逻辑坏块。
1.1、物理坏块
存储介质导致的数据块损坏,常见的场景是磁盘损坏、网络故障导致数据块在传输时发生损坏,突然断电导致数据块的元数据损坏。
1.1.1、物理坏块的常见错误表现
1.1.1.1、Fractured Block(断块)
断块是指数据块不完整,块头信息与块尾信息不匹配。
Corrupt block relative dba: 0x0380e573 (file 14, block 58739)
Fractured block found during buffer read
Data in bad block -
type: 6 format: 2 rdba: 0x0380e573
last change scn: 0x0288.8e5a2f78 seq: 0x1 flg: 0x04
consistency value in tail: 0x00780601
check value in block header: 0x8739, computed block checksum: 0x2f00
spare1: 0x0, spare2: 0x0, spare3: 0x0
***
Reread of rdba: 0x0380e573 (file 14, block 58739) found same corrupted data
1.1.1.2、Bad checksum
这个错误是指checksum不一致。
checksum是数据块的校验和,它是Oracle将数据块中的内容通某种算法进行计算生成的一个唯一的校验值。checksum存储在数据块的头部;当读取数据块时,Oracle会重新计算checksum,与存储在数据块头部的checksum进行比较,如果不一致,就判断数据块发生了损坏。
Corrupt block relative dba: 0x0380a58f (file 14, block 42383)
Bad check value found during buffer read
Data in bad block -
type: 6 format: 2 rdba: 0x0380a58f
last change scn: 0x0288.7784c5ee seq: 0x1 flg: 0x06
consistency value in tail: 0xc5ee0601
check value in block header: 0x68a7, computed block checksum: 0x2f00
spare1: 0x0, spare2: 0x0, spare3: 0x0
***
Reread of rdba: 0x0380a58f (file 14, block 42383) found same corrupted data
1.1.1.3、Block Misplaced
Oracle检测到正在读取的数据块的内容属于另一个块,但是checksum有效。
Corrupt block relative dba: 0x0d805a89 (file 54, block 23177)
Bad header found during buffer read
Data in bad block -
type: 6 format: 2 rdba: 0x0d805b08 ----> Block is different than expected 0x0d805a89
last change scn: 0x0692.86dc08e3 seq: 0x1 flg: 0x04
consistency value in tail: 0x08e30601
check value in block header: 0x2a6e, computed block checksum: 0x0
spare1: 0x0, spare2: 0x0, spare3: 0x0
1.1.1.4、Zeroed out blocks
如果Oracle数据库发生了Zeroed out blocks,那么基本可以判断是操作系统或者存储系统发生了故障。
根据Oracle官方文档描述,在设计的时候,Oracle就被确定不会写全零块,目的是为了识别底层操作系统或者存储中的故障。
Corrupt block relative dba: 0x014000a0 (file 5, block 160)
Completely zero block found during multiblock buffer read
1.2、逻辑坏块
逻辑坏块是指数据块的内容或者结构不符合Oracle内部规范,但是在物理存储介质方面没有问题。虽然发生了逻辑坏块,但其checksum很可能并无问题,只是数据块中的内容发生了损坏。
逻辑坏块的详细损坏描述通常不会输出到告警日志中,这点是和物理坏块不同的。
1.2.1、逻辑坏块常见错误表现:
1.2.1.1、ORA-600 [4512]
ORA-600 [4512],被不存在的事务锁定了行。
1.2.1.2、不符合自然逻辑
日期数据类型字段,存入了2025-03-32 25:61:61这种不符合自然逻辑的数据。
1.2.1.3、数据字典逻辑损坏
Oracle数据字典字典表发生了字典数据与实际不一致的情况;例如:dba_tables显示某表存在,但是实际的数据文件没有这个表段。
执行DDL操作时,触发ORA-00600 [kdsgrp1],或者元数据查询失败。
1.2.1.4、索引失效
索引指向了无效的ROWID,比如说数据行已经被删除了,但是索引没有更新。
索引扫描时,返回ORA-01410(无效的ROWID),或者查询结果不匹配。
1.2.1.5、数据块头部元数据损坏
数据块的头部信息,例如块的类型、SCN、事务槽遭到损坏。例如数据块的类型本来是表段块,但是实际存储的却是索引,这种错误就属于是Oracle软件的bug了。
访问到这种逻辑坏块时,触发ORA-00600 [kddummy_blkchk]内部错误。
2、两个坏块相关的参数
2.1、db_block_checksum
这个参数是用来控制checksum的生成.
checksum是数据块的校验和,它是Oracle将数据块中的内容通某种算法进行计算生成的一个唯一的校验值。checksum存储在数据块的头部;当读取数据块时,Oracle会重新计算checksum,与存储在数据块头部的checksum进行比较,如果不一致,就判断数据块发生了损坏。
可以通过show parameter来查看当前数据库对于此参数的配置情况。
SQL> show parameter db_block_checksum
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
db_block_checksum string TYPICAL
db_block_checksum参数有3种选项:FULL、TYPICAL、OFF,下面对这三种选项进行简单说明。
参数选项 | 简单说明 |
FULL | 对所有数据块,包括数据文件、控制文件、日志文件生成校验和。 |
TYPICAL | 默认值,对数据文件、控制文件生成校验和,不会作用于临时表空间。 |
OFF | 不生成checksum,这个是不推荐的。 |
可以修改db_block_checksum参数,但因为是静态参数,修改完参数需要重启数据库。
ALTER SYSTEM SET db_block_checksum = FULL SCOPE=SPFILE;
2.2、db_block_checking
这个参数是对数据块的逻辑一致性检查,当数据块在被读取或者修改时,自动检测数据块内部结构的逻辑错误,从而防止因为逻辑损坏导致数据丢失。
db_block_checking参数有4种选项:HIGH、MEDIUM、LOW、OFF,下面对这4种选项进行简单的说明。
参数选项 | 简单说明 |
HIGH | 执行完整的逻辑检查,包括索引块、事务槽,覆盖数据库所有的修改操作。 |
MEDIUM | 对所有表空间的数据块执行基本检查,例如块头等。 |
LOW | 只对数据库的系统表空间执行数据块检查。 |
OFF | 不对任何数据块执行检查,这是个默认值。 |
可以修改db_block_checking参数,但因为是静态参数,修改完参数需要重启数据库。
ALTER SYSTEM SET db_block_checking=HIGH SCOPE=SPFILE;
db_block_checksum与db_block_checking参数的不同点在于db_block_checksum是用来检测物理完整性的,主要是服务于物理坏块问题处理;db_block_checking是用来检测逻辑一致性的,主要是服务于逻辑坏块问题处理。
db_block_checksum与db_block_checking参数的相同点是当启用参数时,启用的级别的越高,对性能的影响就会越大。所以在使用这两个参数时,需要在数据库性能和数据安全这两方面做好平衡。
3、检测坏块
Oracle数据库提供了几种手段可以对坏块进行监测,下面对这些手段进行逐一介绍。这些手段侧重点和使用场景都有不同,有的也可以组合使用,效率效果更佳。
3.1、告警日志
告警日志是DBA最常看的日志,每次巡检如果绕开它,个人觉得都不算一次标准的巡检。Oracle在检测到数据坏块时,会在告警日志中记录错误信息。通过检查告警日志,可以及时发现数据坏块错误。
确定告警日志位置,以下两个方法都可以。
SQL> show parameter dump
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
background_core_dump string partial
background_dump_dest string /oracle/app/diag/rdbms/htbase/htbase1/trace
core_dump_dest string /oracle/app/diag/rdbms/htbase/htbase1/cdump
max_dump_file_size string unlimited
shadow_core_dump string PARTIAL
user_dump_dest string /oracle/app/diag/rdbms/htbase/htbase1/trace
SQL> SELECT value FROM v$diag_info WHERE name = 'Diag Trace';
VALUE
--------------------------------------------------------------------------------
/oracle/app/diag/rdbms/htbase/htbase1/trace
检索坏块错误记录
在告警日志中搜索“ORA-01578”,或者搜素“Corrupt block“关键字。这个大家可以看上文物理坏块介绍那里就知道为什么检索两个了。
3.2、RMAN
RMAN也是官方推荐坏块检测工具,支持对数据文件、控制文件、归档日志文件等进行完整性检查,个人觉得RMAN比较适合作为一种定期健康检查的手段。RMAN有下面几种检查方式。
3.2.1、检查整个数据库
可以输出所有数据文件、控制文件、日志文件的坏块信息。
RMAN> VALIDATE DATABASE;
3.2.2、检查指定的数据文件
可以输出数据文件内每个块的物理和逻辑一致性。
RMAN> VALIDATE DATAFILE 28; -- 文件号为28的数据文件
3.2.3、逻辑一致性检查
不仅检查数据块的物理完整性,还会检查数据块的逻辑结构。
RMAN> VALIDATE CHECK LOGICAL DATABASE;
3.2.4、检查备份集
这个命令,无论是新手还是老手都不陌生,备份常用命令,检查备份文件是否损坏,确保恢复的时候是可用的。
RMAN> VALIDATE BACKUPSET <备份集编号>;
3.3、ANALYZE
ANALYZE命令可以检查表、索引的逻辑一致性。
3.3.1、检查表结构
如果存在坏块,会抛出ORA-01498错误。此时可能是数据块头部的元数据信息出错、索引对应数据块的关联关系有误(这些都在上文的逻辑坏块部分介绍过);如果是物理坏块,还会伴随着ORA-01578。
ANALYZE TABLE hr.zqd VALIDATE STRUCTURE;
3.3.2、检查索引的一致性
如果索引与表数据不匹配,会抛出ORA-01499错误。这可能是索引和表数据没同步,例如删除大量数据后,没有重建索引;事务异常中断导致索引条目没有正常更新等等。如果是物理损坏的话,会伴随着ORA-01578错误。
此时比较高效的修复操作是重建索引,但不一定能解决所有场景下的此类问题。
ANALYZE INDEX HR.zqd_idx VALIDATE STRUCTURE;
3.4、数据字典
3.4.1、V$DATABASE_BLOCK_CORRUPTION
SELECT * FROM V$DATABASE_BLOCK_CORRUPTION;
这个视图会记录Oracle数据库中检测到坏块信息。这个视图的数据写入触发机制包含以下几种途径。
操作 | 具体操作 |
RAMN | 1、使用RMAN进行备份的时候,会验证数据块的完整性,若发现坏块,会写入该视图。 2、使用RMAN进行VALIDATE验证时,若发现坏块,会写入该视图。 3、使用RMAN进行RECOVER恢复时,若发现坏块,会写入该视图。 |
进程检测 | 1、读取数据时,checksum不一致,会将坏块信息写入该视图。 2、SELECT语句访问到坏块时,会将坏块信息写入该视图。 |
工具检查 | 1、DBVERIFY作为外部工具,不会直接将坏块信息写入视图,但可以通过日志文件手动确认坏块后,需要通过数据库操作才会将坏块信息写入视图。 2、ANALYZE命令若检测到坏块可能会将坏块信息写入视图。 |
3.4.2、DBA_EXTENTS
通过这个Oracle系统基础表,可以定位到具体的坏块所在对象,可能是表、索引、大字段。
SELECT owner, segment_name, segment_type
FROM dba_extents
WHERE file_id = 5 -- 文件号
AND 1234 BETWEEN block_id AND block_id + blocks -1; -- 块号
3.5、DBVERIFY
DBVERIFY是Oracle数据库提供的命令行工具,用于检查数据文件物理和逻辑一致性,重点检查数据块的完整性。
3.5.1、DBVERIFY有几个特点,主要包含以下几个方面。
3.5.1.1、只读方式打开
它以只读的方式打开数据文件,不会修改数据文件,确保了数据文件的安全性。
3.5.1.2、在线检查
它支持在线检查数据文件,不需要关闭数据库。当然离线时对数据文件的检查速度更快。
3.5.1.3、支持对ASM文件的检查
在数据库是正常打开的情况下,通过userid参数指定用户权限,即可检查ASM文件。
dbv file=+DG1/orcl/datafile/system01.dbf userid=system/sys
3.5.2、DBVERIFY是有局限性的,主要包含以下几个方面:
3.5.2.1、只检查数据文件
它只能检查数据文件;不能检查控制文件和日志文件。
3.5.2.2、不检查逻辑关联性
只会检查数据块内部的结构,不会检查索引与表数据的匹配关系。
3.5.2.3、文件大小限制
在某些平台下,不能检查超过2G的数据文件(但这一点我自己没有实际验证过)。
3.5.3、常用与帮助
DBVERIFY工具的命令参数有很多,但常用的就是FILE、BLOCKSIZE、LOGFILE等。如果有特殊需要可以查看帮助:dbv help=y
dbv FILE=/oradata/users01.dbf BLOCKSIZE=8192 LOGFILE=/tmp/dbv.log
4、修复坏块
4.1、RMAN修复
最理想的是使用RMAN恢复,进而实现修复坏块的目的。但这是需要当前数据库有备份,并且备份可用无坏块的情况下。
4.1.1、使用RMAN进行块恢复
检测到数据库的坏块、坏块所在数据文件后,可以指定坏块进行块恢复。建议是在数据库坏块不多的情况下使用块恢复,这种修复效率很高;如果是恢复整个数据文件,恢复效率会相对很低。
RMAN> VALIDATE BACKUPSET ALL; --检查所有备份集是否可用,且有无坏块。
RMAN> VALIDATE BACKUPSET <备份集编号>; --指定备份集进行检查,检查是否可用,且有无坏块。
RMAN> VALIDATE DATABASE; --检测坏块位置
RMAN> blockrecover datafile 4 block 1798; --进行块恢复
--也可以一次修复同一数据文件中的多个坏块
RMAN> blockrecover datafile 4 block 1798,1799; --进行多个坏块恢复
VALIDATE DATAFILE 4 BLOCK 1798; --验证修复结果
4.1.2、使用RMAN进行数据文件恢复
如果数据库中的坏块很多,或者进行RMAN块恢复失败时,可以尝试使用RMAN进行数据文件恢复。
使用RMAN进行数据文件恢复会有几点事项需要注意:恢复期间,数据库部分不可用,因为坏块所在的数据文件需要OFFLINE;需要确保归档日志完整以支持完全恢复;如果坏块所在的数据文件比较大,恢复效率不会很高。
RMAN> VALIDATE BACKUPSET ALL; --检查所有备份集是否可用,且有无坏块。
RMAN> VALIDATE BACKUPSET <备份集编号>; --指定备份集进行检查,检查是否可用,且有无坏块。
RMAN> VALIDATE DATABASE; --检测坏块位置
SQL> ALTER DATABASE DATAFILE 4 OFFLINE; --离线坏块所在的数据文件
RMAN> RESTORE DATAFILE 4; --从备份恢复数据文件
RMAN> RECOVER DATAFILE 4; --还原坏块所在的数据文件
SQL> ALTER DATABASE DATAFILE 4 ONLINE; --切换数据文件为ONLINE状态。
4.1.3、补充
上文的备份集检查是建议每次备份完就执行检查,定期巡检也加入这类检查。如果备份集不可用或者存在坏块,那么使用RMAN进行坏块修复的这条路就断掉了。
4.2、DBMS_REPAIR修复
Oracle数据库提供了DBMS_REPAIR包用于修复坏块。但这个方法在修复坏块方面往往是不奏效的。拟人化的讲,我个人觉得这个DBMS包有点 “打的过就打,打不过就跑” 的感觉。因为它虽然很多时候修不好坏块,但是它能让Oracle的增删改查操作跳过坏块;这也就导致了使用DBMS_REPAIR方法非常可能会丢失数据。但是在没有备份的情况下,这也是发生坏块时,能拯救部分数据的方法之一。
下图是DBMS_REPAIR的常用方法总结。
现在Oracle数据库中HR.ZQD0318表有个坏块,接下来详细描述下DBMS_REPAIR修复/跳过坏块的步骤。
SQL> SELECT COUNT(*) FROM HR.ZQD0318;
COUNT(*)
----------
72594
4.2.1、创建REPAIR表
REPAIR表用于记录需要被修复的表。REPAIR表创建完后,是不会立刻有数据的;在检查完对象的数据块受损情况后,REPAIR表才会有数据。
BEGIN
DBMS_REPAIR.ADMIN_TABLES(
TABLE_NAME => 'REPAIR_TABLE',
TABLE_TYPE => DBMS_REPAIR.REPAIR_TABLE,
ACTION => DBMS_REPAIR.CREATE_ACTION,
TABLESPACE => 'ZQD');
END;
--如果在SQLPLUS中执行PLSQL代码块,需要在代码块末尾加上“/”
4.2.2、 创建ORPHAN表
ORPHAN表是用来存放在表出现坏块后的孤立(OPRHAN)索引相关信息,也就是指向那些坏块的索引信息。ORPHAN表创建完后,不会立刻有数据;在使用DUMP_ORPHAN_KEYS过程后,才会有指向坏块的索引相关信息。
BEGIN
DBMS_REPAIR.ADMIN_TABLES(
TABLE_NAME => 'ORPHAN_KEY_TABLE',
TABLE_TYPE => DBMS_REPAIR.ORPHAN_TABLE,
ACTION => DBMS_REPAIR.CREATE_ACTION,
TABLESPACE => 'ZQD');
END;
--如果在SQLPLUS中执行PLSQL代码块,需要在代码块末尾加上“/”
4.2.3、检查对象的数据块受损情况
检查完后,坏块信息会写入REPAIR表。
DECLARE
corruptnum INT;
BEGIN
corruptnum := 0;
DBMS_REPAIR.CHECK_OBJECT(
SCHEMA_NAME => 'HR',
OBJECT_NAME => 'ZQD0318',
REPAIR_TABLE_NAME => 'REPAIR_TABLE',
CORRUPT_COUNT => corruptnum);
DBMS_OUTPUT.PUT_LINE('坏块数量:' || TO_CHAR(corruptnum));
END;
--如果在SQLPLUS中执行PLSQL代码块,需要在代码块末尾加上“/”
4.2.4、查看坏块信息
SELECT OBJECT_NAME,BLOCK_ID,CORRUPT_TYPE,MARKED_CORRUPT,REPAIR_DESCRIPTION FROM REPAIR_TABLE;
4.2.5、尝试修复坏块
如果输出是0,则说明修复了0个坏块,修复无效,此时可以选择跳过坏块。
DECLARE
fixnum number;
BEGIN
DBMS_REPAIR.FIX_CORRUPT_BLOCKS(
SCHEMA_NAME => 'HR',
OBJECT_NAME => 'ZQD0318',
FIX_COUNT => fixnum);
DBMS_OUTPUT.PUT_LINE('修复坏块的数量:' || TO_CHAR(fixnum));
END;
--如果在SQLPLUS中执行PLSQL代码块,需要在代码块末尾加上“/”
4.2.6、 记录指向坏块的索引相关信息
执行完如下的PLSQL代码块后,失效索引相关信息(指向坏块的索引相关信息)会写入ORPHAN表。PLSQL代码块中的OBJECT_NAME是索引的名字,不是表名;如果有多个索引,需要为每个索引执行DUMP_ORPHAN_KEYS操作。
DECLARE
DATAS NUMBER;
BEGIN
DBMS_REPAIR.DUMP_ORPHAN_KEYS(
SCHEMA_NAME => 'HR',
OBJECT_NAME => 'IDX_ZQD',
OBJECT_TYPE => DBMS_REPAIR.INDEX_OBJECT,
REPAIR_TABLE_NAME => 'REPAIR_TABLE',
ORPHAN_TABLE_NAME => 'ORPHAN_KEY_TABLE',
KEY_COUNT => COUNTS);
DBMS_OUTPUT.PUT_LINE('孤立索引数量:' || TO_CHAR(COUNTS));
END;
4.2.7、跳过坏块
执行下面PLSQL代码块后,坏块已被标记跳过。此时增删改查都不会报错,但坏块上的这部分数据也就丢失了。但丢失的数据的ROWID依然可以在ORPHAN表找到。
BEGIN
DBMS_REPAIR.SKIP_CORRUPT_BLOCKS(
SCHEMA_NAME => 'HR',
OBJECT_NAME => 'ZQD0318',
OBJECT_TYPE => DBMS_REPAIR.TABLE_OBJECT,
FLAGS => DBMS_REPAIR.SKIP_FLAG);
END;
--如果在SQLPLUS中执行PLSQL代码块,需要在代码块末尾加上“/”
4.2.8、重建索引
ALTER INDEX HR.IDX_ZQD REBUILD
4.2.9、验证结果
SQL> SELECT COUNT(*) FROM HR.ZQD0318;
COUNT(*)
----------
72542
4.2.10、后续
上面的一套组合拳打完,表虽然恢复了访问、但是也丢失了部分数据。即使丢失的数据可以通过其他方式补回来(例如数据仓库可以再重新抽取数据),也存在一个问题:坏块依然存在。
此时,将表中的数据全部导出(导出时跳过了坏块数据),然后重建表;再将导出的数据重新导入到数据库中;这个过程建议是使用数据泵expdp/impdp操作。操作完毕后,可以彻底消除坏块。
4.3、BBED
BBED:Block Browser and Editor(块浏览器和编辑器)
BBED直接操作底层数据块,风险很高,需要非常专业的人士来操作。且操作不可逆,是作为坏块修复的最后一种手段使用。
5、写在最后
上面简单介绍了坏块的3种修复手段;但当真在生产上遇到了坏块情况,仅凭这篇文章的描述,很难高效解决问题。这篇文章只是基础了解,想要高效解决坏块问题,除了基础以外,需要平时多做模拟演练,定期总结,才能在遇到坏块时,快速给出最完美的解决方案。