暂无图片
暂无图片
4
暂无图片
暂无图片
暂无图片

[MYSQL] mysql坏块检查

原创 大大刺猬 2024-08-22
516

导读

当mysql存在坏块的时候, 查询对应的表就会报错,然后数据库就crash了. 比如:
image.png
也就是只有我们查询有坏块的表的时候才会发现有坏块,启动的时候并不会做坏块检查, 那么我们要怎么知道数据库有哪些表有坏块了呢? 有坏块后怎么处理呢?

innochecksum

mysql提供了一个工具innochecksum来检查数据块.
正常情况下, 打印页信息, 比如:

(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]#

如果是坏块的话, 打印信息则如下:

(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
image.png
为了安全, 也就将就把. 所以本文就结束了. 感谢观看!
image.png

坏块校验原理

有时候我们并不能关闭数据库, 但就是想要校验坏块, 总不能去查询所有表吧, 而且如果有坏块的话, 数据库就挂了啊. 这还得了.

那就只能来挖innodbchecksum的源码了. 看下校验原理, 然后我们自己写脚本来校验. 我们还是使用万能的gdb调试来做.

(echo -e "break main\nrun /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的完整堆栈信息了. 稍加整理就能得到调用过程:
image.png

也就是最终还是走的crc32校验. 相关代码如下:

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

不好理解, 我们画个图吧.
image.png
也就是只校验了一部分header和所有data, 连PAGE_TYPE,SPACE_ID之类的均未校验.

难道就这么简单么. 我们来测试下吧.
crc32算法我们还是参考之前解析checksum table命令时的算法, 比较都是mysql的嘛. (关于ibd的结构, 请查看: https://www.modb.pro/topic/625137)

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))

image.png

啊咧咧, 咋个不一样呢.

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
image.png
看这篇文章时我的感受是:
mb.gif

好在计算机也是个笨蛋, 程序员要能让计算机看懂指令, 就得写简单的代码. 从代码来看基本上激素查个表而已. 我们直接使用python重写

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的魅力吧.

那我们再次验证下呢.
image.png

测试

把我们整理成脚本,来测试下吧.
首先构造一个有坏块的文件. 如果你有的话, 就不用这一步了.

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()

然后校验正常的文件:
image.png
然后校验异常的文件:
image.png

坏的块确实校验出来了, 也是我们故意损坏的位置. 说明我们的校验工具没问题(棒棒哒!)

总结

  1. mysql ibd文件的坏块校验 就是FIL_HEADER的crc32c值^FIL_DATA的crc32c值. 然后和文件头/尾保存的crc32值比较即可.
  2. CRC32-C 其实有现成的库, 可以使用pip install crc32c去安装.
  3. 遇到坏块的话, 可以使用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
这次写得比较简单, 都没做选项解析之类的.

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'\x00\x00\x00\x00' and data[26:28] == b'\x00\x00': 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)
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论