前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[MYSQL] mysql坏块检查

[MYSQL] mysql坏块检查

原创
作者头像
大大刺猬
发布2024-08-22 15:31:17
260
发布2024-08-22 15:31:17
举报
文章被收录于专栏:大大刺猬

导读

当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调试来做.

代码语言:shell
复制
(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的完整堆栈信息了. 稍加整理就能得到调用过程:

也就是最终还是走的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()

然后校验正常的文件:

然后校验异常的文件:

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

总结

  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

这次写得比较简单, 都没做选项解析之类的.

代码语言: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'\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)

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 导读
  • innochecksum
  • 坏块校验原理
  • CRC32C
  • 测试
  • 总结
  • 附源码
相关产品与服务
云数据库 MySQL
腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档