概念澄清
asm_preferred_read_failure_groups 参数能修复坏块,这其实是一个常见的误解。该参数本身并不具备修复功能,真正修复坏块是 ASM 冗余磁盘组自有的机制。这个参数的作用是触发修复坏块的动作,其本质是告诉 ASM 实例优先读取哪个故障组 (failgroup)。
在冗余磁盘组中,数据并非只有一份。NORMAL 冗余度的磁盘组数据有 2 份冗余,HIGH 冗余度的则有 3 份。这些冗余副本中,一份是主副本 (primary),其余为镜像副本 (mirror)。默认情况下,Oracle 读取数据时会优先读取主副本所在的故障组。而当我们设置了 asm_preferred_read_failure_groups 参数后,则会优先读取该参数指定的故障组。
修复坏块是冗余磁盘组与生俱来的能力:当第一次尝试读取的数据块损坏时,系统会自动从其他冗余副本中读取。如果其他副本是完好的,便会自动修复那个损坏的数据块。因此,即便不设置 asm_preferred_read_failure_groups 参数,主副本损坏也能被自动修复。此参数的关键作用,在于当镜像副本损坏时,通过引导系统去读取完好的主副本来触发修复流程。
测试验证
下面我们通过几个具体场景,来演示如何利用 asm_preferred_read_failure_groups 参数处理坏块。我们将模拟以下三种情况:
- 主副本完好,镜像副本损坏,且
asm_preferred_read_failure_groups 参数为空。
- 主副本损坏,镜像副本完好,且
asm_preferred_read_failure_groups 参数为空。
- 主副本损坏,镜像副本完好,并设置了
asm_preferred_read_failure_groups 参数。
在开始实验前,需要先明确几个关键知识点:
- 如何确定主副本 (primary) 和镜像副本 (mirror) 的分布位置。
- 数据文件的块 (block) 如何对应到磁盘组的分配单元 (AU)。
- 主副本、镜像副本与故障组之间的对应关系。
这些问题将在后续实验中逐一揭晓。
环境准备与块位置定位
首先,查询测试所用的磁盘组和数据文件信息。
-- 查询磁盘组
SQL> select group_number, name from v$asm_diskgroup;
-- 查询DATA磁盘组的磁盘详情
select disk_number,name,path,failgroup from v$asm_disk where group_number = 2 order by failgroup,disk_number;
查询结果示例如下:
DISK_NUMBER NAME PATH FAILGROUP
----------- -------------------- -------------------- ----------
0 DATA_0000 /dev/sdf FG1
1 DATA_0001 /dev/sdg FG1
2 DATA_0002 /dev/sdh FG2
3 DATA_0003 /dev/sdi FG2
确定测试数据文件。
-- 数据库内查询数据文件
SQL> select file#,name from v$datafile;
-- ASM实例内查询文件别名
select file_number,name from v$asm_alias where name like '%TESTBS%';
接下来,查询目标数据文件(FILE_NUMBER=266)在磁盘组中的具体分布,即每个区 (extent) 的主、镜像副本位于哪个磁盘的哪个AU。
set linesize 140 pagesize 1400
col "FILE_NAME" format a30
set head on
select NAME "FILE_NAME",
NUMBER_KFFXP "FILE_NUMBER",
XNUM_KFFXP "EXTENT_NUMBER",
LXN_KFFXP,PXN_KFFXP ,
DISK_KFFXP "DISK_NUMBER",
AU_KFFXP "AU_NUMBER",
SIZE_KFFXP "NUMBER_of_AUs"
from x$kffxp, v$asm_alias
where GROUP_KFFXP = GROUP_NUMBER
and NUMBER_KFFXP = FILE_NUMBER
and system_created = 'Y'
and NUMBER_KFFXP=266
and GROUP_KFFXP = 2
order by XNUM_KFFXP,LXN_KFFXP;
查询结果片段如下(LXN_KFFXP=0 为主副本,1为镜像副本):
FILE_NAME FILE_NUMBER EXTENT_NUMBER LXN_KFFXP PXN_KFFXP DISK_NUMBER AU_NUMBER NUMBER_of_AUs
------------------------------ ----------- ------------- ---------- ---------- ----------- ---------- -------------
TESTBS.266.1213453021 266 0 0 0 1 21 1
TESTBS.266.1213453021 266 0 1 1 3 25 1
TESTBS.266.1213453021 266 1 0 2 3 26 1
TESTBS.266.1213453021 266 1 1 3 1 22 1
...
由此我们可以知道每一个区的主、镜像副本具体分布在哪个磁盘(DISK_NUMBER)的哪个AU(AU_NUMBER)上。
然后,在数据库中找到我们想要测试的特定数据块。例如,找到 TEST.OBJECT 表段使用的区段,并选择其中一个块进行测试(假设为6396号块)。
select file_id,block_id,blocks from dba_extents where segment_name='OBJECT' and owner='TEST' order by block_id;
确认该块内实际存储的数据内容。
set serveroutput on
exec print_table('select * from object where dbms_rowid.ROWID_BLOCK_NUMBER(rowid) = 6396 and dbms_rowid.ROWID_OBJECT(rowid) = 23366 and dbms_rowid.ROWID_RELATIVE_FNO(rowid) = 6');
通过 asmcmd 将数据文件拷贝出来,并用 dd 和 od 命令验证块内容,可以找到特定的字符串(如 KUPC$_METADATA_FILTER 的16进制表示),以便后续对照。
计算 6396 号块对应在磁盘组中的 extent 和 block。
- 公式:
block_number * block_size / au_size
- 示例:
6396 * 8192 / 4194304 = 12.4921875
- 这意味着它在第12个区(EXTENT_NUMBER=12)。
- 计算区内的偏移:
0.4921875 * 4194304 = 2,064,384 字节
- 转换为8K块:
2,064,384 / 8192 = 252
- 即该块位于第12个区的第252个块。
根据之前 x$kffxp 的查询结果,EXTENT_NUMBER=12 有两行,对应主副本和镜像副本:
FILE_NAME FILE_NUMBER EXTENT_NUMBER LXN_KFFXP PXN_KFFXP DISK_NUMBER AU_NUMBER NUMBER_of_AUs
------------------------------ ----------- ------------- ---------- ---------- ----------- ---------- -------------
TESTBS.266.1213453021 266 12 0 24 1 27 1
TESTBS.266.1213453021 266 12 1 25 2 32 1
由此可知:
- 主副本(LXN_KFFXP=0)在 DISK_NUMBER=1(即
/dev/sdg)的 AU 27 上。
- 镜像副本(LXN_KFFXP=1)在 DISK_NUMBER=2(即
/dev/sdh)的 AU 32 上。
结合磁盘列表,可知主副本在故障组 FG1,镜像副本在故障组 FG2。
直接读取物理磁盘上的这两个块进行验证:
# 读取主副本
dd if=/dev/sdg bs=4M skip=27 count=1|dd bs=8k skip=252 count=1 | dd bs=8k skip=0 count=1|od -v -x
# 读取镜像副本
dd if=/dev/sdh bs=4M skip=32 count=1|dd bs=8k skip=252 count=1 | dd bs=8k skip=0 count=1|od -v -x
两者输出内容应完全一致,且包含我们之前确认的数据特征。
为了方便后续的破坏和恢复,先备份这个数据块在ASM文件中的映像,以及其在两个物理磁盘上的原始位置。
-- 在ASM实例中运行脚本,将ASM中的指定块提取到文件系统
-- 脚本会提示输入ASM文件名、块号等,此处示例为备份6396块
@asmtofs
-- 根据提示输入:
-- ASM_File_Name: +DATA/PROD/DATAFILE/testbs.266.1213453021
-- block_to_extract: 6396
-- number_of_blocks_to_extract: 1
-- FileSystem_File_Name: /home/grid/enmo/6_6396.blk
# 备份物理磁盘上的主副本块
dd if=/dev/sdg bs=4M skip=27 count=1|dd bs=8k skip=252 count=1 of=/home/grid/enmo/sdg_au27_252_primary.blk
# 等价于(计算AU内偏移为8K块的序号): 27*4096/8+252 = 14076
dd if=/dev/sdg bs=8k skip=14076 count=1 of=/home/grid/enmo/sdg_au27_252_primary.blk
# 备份物理磁盘上的镜像副本块
dd if=/dev/sdh bs=4M skip=32 count=1|dd bs=8k skip=252 count=1 of=/home/grid/enmo/sdh_au32_252_mirror.blk
# 等价于: 32*4096/8+252 = 16636
dd if=/dev/sdh bs=8k skip=16636 count=1 of=/home/grid/enmo/sdh_au32_252_mirror.blk
场景一:主副本好,镜像坏,参数为空
首先确认 asm_preferred_read_failure_groups 参数为空。
su - grid
sqlplus / as sysasm
show parameter asm_preferred_read_failure_groups;
破坏镜像副本所在磁盘的对应块。
dd if=/dev/zero of=/dev/sdh bs=8k seek=16636 count=1 conv=notrunc
# 验证已被破坏
dd if=/dev/sdh bs=8k skip=16636 count=1|od -x | head -5
在数据库中读取该块。由于参数未设置,默认会优先读取主副本(FG1),而主副本是完好的,所以读取会成功,且不会触发修复镜像的操作。
su - oracle
sqlplus / as sysdba
alter system checkpoint;
alter system flush buffer_cache;
alter system flush shared_pool;
conn test/test
set serveroutput on
exec print_table('select * from object where dbms_rowid.ROWID_BLOCK_NUMBER(rowid) = 6396 ...');
此时查询正常返回数据,且告警日志 (alert log) 中不会出现相关错误。
最后,还原镜像副本块。
dd if=/home/grid/enmo/sdh_au32_252_mirror.blk of=/dev/sdh bs=8k seek=16636 count=1 conv=notrunc
场景二:主副本坏,镜像好,参数为空
破坏主副本所在磁盘的对应块。
dd if=/dev/zero of=/dev/sdg bs=8k seek=14076 count=1 conv=notrunc
在数据库中再次读取该块。由于默认读取主副本时遇到坏块,ASM 的冗余机制会自动去读取完好的镜像副本,并利用镜像副本的数据修复主副本。
-- 在oracle用户下,再次清空缓存并查询
su - oracle
sqlplus / as sysdba
alter system checkpoint;
alter system flush buffer_cache;
alter system flush shared_pool;
conn test/test
set serveroutput on
exec print_table('select * from object where dbms_rowid.ROWID_BLOCK_NUMBER(rowid) = 6396 ...');
此次查询仍然成功。检查告警日志,会发现类似以下的记录,证实了读取坏块、转向镜像、并成功修复的过程:
Corrupt block relative dba: 0x018018fc (file 6, block 6396)
Completely zero block found during multiblock buffer read
Reading datafile '+DATA/PROD/DATAFILE/testbs.266.1213453021' for corrupt data at rdba: 0x018018fc (file 6, block 6396)
Read datafile mirror 'DATA_0001' (file 6, block 6396) found same corrupt data (no logical check)
Read datafile mirror '' (file 6, block 6396) found valid data
Repaired corruption at (file 6, block 6396)
这说明,即使不设置 asm_preferred_read_failure_groups 参数,只要冗余副本中有完好的数据,坏块就能被自动修复。
场景三:利用参数修复镜像坏块
现在,我们模拟镜像副本损坏,而主副本完好的情况(即场景一的状态),但这次希望通过设置参数来触发修复。
首先,确保镜像副本是损坏的。
dd if=/dev/zero of=/dev/sdh bs=8k seek=16636 count=1 conv=notrunc
此时,使用 dbv 工具可以检测到坏块,但数据库的 V$DATABASE_BLOCK_CORRUPTION 视图可能不会立即显示,因为主副本是好的,数据库正常读取时不会报错。
现在,设置 asm_preferred_read_failure_groups 参数,让实例优先读取镜像副本所在的故障组 FG2。
su - grid
sqlplus / as sysasm
alter system set asm_preferred_read_failure_groups='DATA.FG2' sid='+ASM1';
show parameter asm_preferred_read_failure_groups;
设置后,当数据库再次读取该数据块时,由于参数指引,它会尝试优先从 FG2(镜像副本)读取,但发现其损坏,于是自动转向完好的主副本(FG1)读取数据,并在此过程中修复损坏的镜像副本。
su - oracle
sqlplus / as sysdba
alter system checkpoint;
alter system flush buffer_cache;
alter system flush shared_pool;
conn test/test
set serveroutput on
exec print_table('select * from object where dbms_rowid.ROWID_BLOCK_NUMBER(rowid) = 6396 ...');
查询成功。告警日志中会出现类似的修复记录,这次是读取 DATA_0002(镜像)发现损坏,然后从另一镜像(此处指主副本)找到有效数据并修复。
Corrupt block relative dba: 0x018018fc (file 6, block 6396)
...
Read datafile mirror 'DATA_0002' (file 6, block 6396) found same corrupt data (no logical check)
Read datafile mirror '' (file 6, block 6396) found valid data
Repaired corruption at (file 6, block 6396)
验证镜像副本块已被修复。
dd if=/dev/sdh bs=8k skip=16636 count=1|od -x | grep -A1 -B1 \"554b\"
应能看到原始数据已恢复。
知识总结
- 参数适用性:
asm_preferred_read_failure_groups 参数对于非冗余磁盘组没有意义,因为它依赖于多副本机制。
- 核心作用澄清:该参数本身并不修复坏块,修复功能是冗余磁盘组自带的。参数的作用是引导读取优先级,从而在特定场景下(如镜像副本损坏但未被默认读取时)触发磁盘组自身的修复机制。
- 性能影响:此参数对冗余磁盘组的写入操作性能没有提升。但对于读取操作,特别是在扩展RAC (Extended RAC) 或特定地理分布的架构中,通过设置让实例优先读取本地站点的故障组,可以显著减少读取延迟,提升性能。
- 运维意义:理解此参数的真实工作原理,有助于数据库管理员在遇到存储层问题时,做出正确的诊断和干预,而非错误地依赖某个“修复参数”。正确的运维策略应立足于对冗余机制的理解。