导读
当mysql存在坏块的时候, 查询对应的表就会报错,然后数据库就crash了. 比如:
也就是只有我们查询有坏块的表的时候才会发现有坏块,启动的时候并不会做坏块检查, 那么我们要怎么知道数据库有哪些表有坏块了呢? 有坏块后怎么处理呢?
innochecksum
mysql提供了一个工具innochecksum
来检查数据块.
正常情况下, 打印页信息, 比如:
代码语言:shell复制(venv) 14:03:07 [root@ddcw21 mysql-8.0.37]#innochecksum /tmp/t20240612.ibd -S
File::/tmp/t20240612.ibd
================PAGE TYPE SUMMARY==============
#PAGE_COUNT PAGE_TYPE
===============================================
1 Index page
1 SDI Index page
0 Undo log page
1 Inode page
0 Insert buffer free list page
2 Freshly allocated page
1 Insert buffer bitmap
0 System page
0 Transaction system page
1 File Space Header
0 Extent descriptor page
0 BLOB page
0 Compressed BLOB page
0 Subsequent Compressed BLOB page
0 SDI BLOB page
0 Compressed SDI BLOB page
0 Other type of page
===============================================
Additional information:
Undo page type: 0 insert, 0 update, 0 other
Undo page state: 0 active, 0 cached, 0 to_free, 0 to_purge, 0 prepared, 0 other
(venv) 14:03:09 [root@ddcw21 mysql-8.0.37]#
如果是坏块的话, 打印信息则如下:
代码语言:shell复制(venv) 14:03:56 [root@ddcw21 mysql-8.0.37]#innochecksum /tmp/test_badpage_20240822.ibd -S
Fail: page 4 invalid
Exceeded the maximum allowed checksum mismatch count::0
也就是可以使用innochecksum
来检查数据库是否存在坏块, 该工具要求数据库停止运行. 即要停库后再检查.不然会有如下报错:fcntl: Resource temporarily unavailable
为了安全, 也就将就把. 所以本文就结束了. 感谢观看!
坏块校验原理
有时候我们并不能关闭数据库, 但就是想要校验坏块, 总不能去查询所有表吧, 而且如果有坏块的话, 数据库就挂了啊. 这还得了.
那就只能来挖innodbchecksum
的源码了. 看下校验原理, 然后我们自己写脚本来校验. 我们还是使用万能的gdb调试来做.
(echo -e "break mainnrun /data/mysql_dev/data/db1/t20240612.ibd -S -C crc32"; while true;do echo 'step';done) |gdb /root/mysql_source/mysql-8.0.37/bldx86/runtime_output_directory/innochecksum > /tmp/t20240822_innochecksum.gdb.txt 2>&1
然后我们就得到了innochecksum的完整堆栈信息了. 稍加整理就能得到调用过程:
也就是最终还是走的crc32校验. 相关代码如下:
代码语言:c 复制uint32_t buf_calc_page_crc32(const byte *page,
bool use_legacy_big_endian /* = false */) {
ut_crc32_func_t crc32_func =
use_legacy_big_endian ? ut_crc32_legacy_big_endian : ut_crc32;
const uint32_t c1 = crc32_func(page FIL_PAGE_OFFSET,
FIL_PAGE_FILE_FLUSH_LSN - FIL_PAGE_OFFSET);
const uint32_t c2 =
crc32_func(page FIL_PAGE_DATA,
UNIV_PAGE_SIZE - FIL_PAGE_DATA - FIL_PAGE_END_LSN_OLD_CHKSUM);
return (c1 ^ c2);
}
也就是 对FIL_PAGE_HEADER做crc的结果^FIL_PAGE_DATA 即为我们需要的crc32值.
FIL_PAGE_OFFSET之类的可以查看我之前写的文章: https://www.modb.pro/topic/625137
不好理解, 我们画个图吧.
也就是只校验了一部分header和所有data, 连PAGE_TYPE,SPACE_ID之类的均未校验.
难道就这么简单么. 我们来测试下吧.
crc32算法我们还是参考之前解析checksum table命令时的算法, 比较都是mysql的嘛. (关于ibd的结构, 请查看: https://www.modb.pro/topic/625137)
代码语言:python代码运行次数:0复制import struct,binascii
filename = '/tmp/t20240612.ibd'
f = open('/tmp/t20240612.ibd','rb')
data = f.read(16384)
checksum_field1 = struct.unpack('>L',data[:4])[0]
checksum_field2 = struct.unpack('>L',data[-8:-4])[0]
c1 = binascii.crc32(data[4:26])
c2 = binascii.crc32(data[38:16384-8])
print(checksum_field1,checksum_field2,(c1^c2)&(2**32-1))
啊咧咧, 咋个不一样呢.
CRC32C
看来和那个ut_crc32
有关, 应该不是普通的crc32. 不然就直接调zlib的crc32了. 干嘛还写这么麻烦呢. 当我们细看storage/innobase/ut/crc32.cc
的实现的时候, 发现实际上是CRC32-C(Cyclic Redundancy Check 32-bit Castagnoli), 与普通的crc32相比, 使用了不同的生成多项式. 找到篇相关博文, 讲得很细, 但看起来比较费劲: https://blogs.oracle.com/mysql/post/faster-crc32-c-computation-in-mysql-8027
看这篇文章时我的感受是:
好在计算机也是个笨蛋, 程序员要能让计算机看懂指令, 就得写简单的代码. 从代码来看基本上激素查个表而已. 我们直接使用python重写
代码语言:python代码运行次数:0复制import struct
def create_crc32c_table():
poly = 0x82f63b78
table = []
for i in range(256):
crc = i
for _ in range(8):
if crc & 1:
crc = (crc >> 1) ^ poly
else:
crc >>= 1
table.append(crc)
return table
def calculate_crc32c(data):
crc = 0xFFFFFFFF
for byte in data:
crc = crc32_slice_table[(crc ^ byte) & 0xFF] ^ (crc >> 8)
return crc ^ 0xFFFFFFFF
crc32_slice_table = create_crc32c_table()
虽然我们看不到代码, 但是能使用python重写, 这就是python的魅力吧.
那我们再次验证下呢.
测试
把我们整理成脚本,来测试下吧.
首先构造一个有坏块的文件. 如果你有的话, 就不用这一步了.
代码语言:python代码运行次数:0复制f1 = open('/data/mysql_dev/data/db1/t20240612.ibd','rb')
f2 = open('/tmp/test_badpage_20240822.ibd','wb')
alldata = f1.read()
baddata = alldata[:16384*4 100] 100*b'ddcw' alldata[16384*4 100 100*4:]
f2.write(baddata)
f2.close()
f1.close()
然后校验正常的文件:
然后校验异常的文件:
坏的块确实校验出来了, 也是我们故意损坏的位置. 说明我们的校验工具没问题(棒棒哒!)
总结
- mysql ibd文件的坏块校验 就是FIL_HEADER的crc32c值^FIL_DATA的crc32c值. 然后和文件头/尾保存的crc32值比较即可.
- CRC32-C 其实有现成的库, 可以使用
pip install crc32c
去安装. - 遇到坏块的话, 可以使用ibd2sql工具去解析还正常的页的数据. 用法讲过很多次了. 就不再介绍了.
参考:
https://dev.mysql.com/doc/refman/8.0/en/innochecksum.html
https://blogs.oracle.com/mysql/post/faster-crc32-c-computation-in-mysql-8027
附源码
github地址: https://github.com/ddcw/ddcw/tree/master/python/check_innodb_file
这次写得比较简单, 都没做选项解析之类的.
代码语言:python代码运行次数:0复制import struct
import sys,os
def create_crc32c_table():
poly = 0x82f63b78
table = []
for i in range(256):
crc = i
for _ in range(8):
if crc & 1:
crc = (crc >> 1) ^ poly
else:
crc >>= 1
table.append(crc)
return table
def calculate_crc32c(data):
crc = 0xFFFFFFFF
for byte in data:
crc = crc32_slice_table[(crc ^ byte) & 0xFF] ^ (crc >> 8)
return crc ^ 0xFFFFFFFF
crc32_slice_table = create_crc32c_table()
filename = sys.argv[1]
if not os.path.exists(filename):
print(f'USAGE: python sys.argv[0] xxx.ibd')
sys.exit(1)
f = open(filename,'rb')
PAGENO = -1
while True:
data = f.read(16384)
PAGENO = 1
if data == b'':
break
if data[:4] == b'x00x00x00x00' and data[26:28] == b'x00x00':
continue # 未使用的页
checksum_field1 = struct.unpack('>L',data[:4])[0]
checksum_field2 = struct.unpack('>L',data[-8:-4])[0]
c1 = calculate_crc32c(data[4:26])
c2 = calculate_crc32c(data[38:16384-8])
#print('PAGENO:',PAGENO,"CHECKSUM:",checksum_field1,checksum_field2,(c1^c2)&(2**32-1))
if checksum_field1 == checksum_field2 == (c1^c2)&(2**32-1):
pass # 正常就不打印了, 不然太多
else:
print("BAD PAGE:",PAGENO)