暂无图片
暂无图片
1
暂无图片
暂无图片
暂无图片

[MYSQL] mysql checksum table原理深度分析

原创 大大刺猬 2024-12-27
531

导读

之前我们简单介绍过checksum table的原理, 总结就是: 各行的校验值加起来就是最终的结果.
image.png

但实际会复杂一丢丢.(比如null bitmask,int类型等,甚至还有逻辑顺序和物理顺序之分). 所以我们来一点点分析.
image.png

深度分析

我们先要明白一点, checksum是server层做的, 也就是存储引擎得返回逻辑数据给server层去做计算. 所以只解析innodb数据来计算是不对的(主要是int的转换)

null bitmask

现象

先来看看nullable的影响, nullable即表示字段是否为空.

create table test_null(name varchar(20)); insert into test_null values('ddcw'); create table test_notnull(name varchar(20) not null); insert into test_notnull values('ddcw'); checksum table test_null,test_notnull;

我们可以看到明明插入的一样的数据, checksum值居然不一样
image.png

分析

这就说明和是否为空有关系. 我们查看源码 发现会先对null mask做crc32校验
image.png

简单来说就是: 256 - (1<<最后一个null bitmask的位置(0-7)) 得到null_mask, 然后把null_bytes的最后一字节改为null_mask, 如果存在HA_OPTION_PACK_RECORD(字符串) 就给null_bytes的第一字节+1. 最后对这个null_bytes做crc32, 作为第0个字段的crc32值.

比较绕, 也比较花里胡哨. 我们使用python代码来表示就清楚多了

def get_nullbitmask_crc32(null_count,null_bitmask,status=True): null_bitmask = bytearray(null_bitmask[::-1]) # 先来个两级反转 if status: null_mask = 256 - (1 << (null_count+1)%8 ) null_bitmask[-1] = null_mask null_bitmask[0] |= 1 else: null_mask = 256 - (1 << null_count%8 ) null_bitmask[-1] = null_mask return binascii.crc32(bytes(null_bitmask))

null_count: 就是表有多少个字段可以为空.
null_bitmask 就是用来表示实际行是否为空的字节. (innodb是大端字节序, mysql是小端字节序, 所以要先反转一下, 后面的int类型也是这样)
status: 对应HA_OPTION_PACK_RECORD 我们可以使用ibd2sdi查看
image.png
当存在varchar(可溢出字段)时, pack_record=1 否则pack_record=0 (8.0.28之前的checksum table的bug也是因为这玩意…)

status:True 表示没得varchar False:表示有varchar 使用的时候注意下

验证

那我们来验证一波

import binascii null_count = 1 null_bitmask = b'\x00' status = False null_crc32 = get_nullbitmask_crc32(null_count,null_bitmask,status) row_crc32 = binascii.crc32(b'ddcw',null_crc32) print('有nullable的表CRC32:',row_crc32) row_crc32 = binascii.crc32(b'ddcw',0) print('not null的表CRC32:',row_crc32)

和上面checksum table的值对应上了. 说明逻辑没得问题.
image.png

INT相关类型

接下来就是看数据类型了, 主要是int类型存在大小端问题, 字符串是没得这种问题的. 我们知道innodb是小端字节序, mysql 是大端自己序. 所以修改的时候我们只需要简单的反转一下再注意下符号即可.

现象

我们来看下一样数据的两个表的crc32值

create table test_int(id int); insert into test_int values(1); create table test_int_unsigned(id int unsigned); insert into test_int_unsigned values(1); checksum table test_int,test_int_unsigned;

我们看到数据是相同的.
image.png

分析

我们知道 innodb中有符号的1应该是b'\x80\x00\x00\x01' 没得符号的1应该是b'\x00\x00\x00\x01' 所以计算crc32之前需要转换成mysql的小端数据. 直接上python代码吧. 我这里就直接计算crc32值了.

def int2crc32(bdata,c=0,unsigned=False): """ innodb的int计算crc32值(checksum是mysql server做的, 所以不能直接crc32 innodb的, 要先转换一下) bdata: innodb记录的int,bigint的二进制数据 其它的均直接binascii.crc32(bdata,crc32) c: crc32值(int) unsigned: 是否有符号, 默认有符号 """ bdata = bytearray(bdata[::-1]) # mysql:小端好 innodb:大端好 if not unsigned: bdata[-1] += 128 if bdata[-1] < 128 else -128 return binascii.crc32(bytes(bdata),c)

验证

然后我们来验证验证下

import binascii signed_int = b'\x80\x00\x00\x01' unsigned_int = b'\x00\x00\x00\x01' null_crc32 = get_nullbitmask_crc32(1,b'\x00',True) print('有符号的int CRC:',int2crc32(signed_int,null_crc32,False)) print('无符号的int CRC:',int2crc32(unsigned_int,null_crc32,True))

我们可以看到也是和mysql校验的一样的.
image.png

逻辑顺序

innodb是按照 主键,普通字段存储的, 但表结构主键可能在普通字段后面. 但既然是server层校验, 那么就应该和索引位置无关, 即按照逻辑顺序来. 对于做过INSTANT DDL的也需要注意这个问题.
我们调整下索引位置即可.

create table test_logic_pos(aa varchar(200) primary key, bb varchar(200)); insert into test_logic_pos values('aa','bb'); create table test_physical_pos(aa varchar(200), bb varchar(200) primary key); insert into test_physical_pos values('aa','bb'); checksum table test_logic_pos,test_physical_pos;

我们看到校验值是一样的, 说明确实是按照逻辑顺序来的
image.png

生成字段

校验的时候是否会考虑生成字段呢?

现象

create table test_base(id int); create table test_gen_stored(id int,gen_stored INT GENERATED ALWAYS AS (id + 1) STORED); create table test_gen_virtual(id int,gen_stored INT GENERATED ALWAYS AS (id + 1) virtual); insert into test_base values(1); insert into test_gen_stored(id) values(1); insert into test_gen_virtual(id) values(1); checksum table test_base,test_gen_stored,test_gen_virtual;

我们发现生成字段数据是一样的, 且是计算了生成字段的. (毕竟server层做的计算)
image.png

分析

对于stored和virtual是一样的就说明virtual也会占用null bitmask但不占用存储(花里胡哨的). stored不但占用null bitmask还会占用存储.
image.png
image.png

验证

然后我们使用python来验证下

null_crc32 = get_nullbitmask_crc32(2,b'\x00',True) int2crc32(b'\x80\x00\x00\x02',int2crc32(b'\x80\x00\x00\x01',null_crc32,False),False)

计算值确实和checksum 一致. 而null count 我们均是按照2(含生成列)来计算的.
image.png

总结

从上面的各例子看, checksum table校验是数据是逻辑的二进制数据. 即存储引擎层返回数据给server层, server层按照表字段顺序做校验, 并将最终的校验值加起来作为最终checksum值.

既然知道了更深层的原理, 那么我们就可以写存储过程或者脚本来自己实现checksum table的校验了. 存储过程的话, 主要是得判断字段能否为空和是否为空, 然后先计算null bitmask的crc32值, 然后按照字段顺序校验. 比如:
image.png

测试了下, 自己写的脚本速度不如官方的(开到4并发都没追上,py的性能还是堪忧啊). 汇总如下
image.png

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

评论