首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >[MYSQL] mysql checksum table原理深度分析

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

原创
作者头像
大大刺猬
发布2024-12-27 16:00:47
发布2024-12-27 16:00:47
52100
代码可运行
举报
文章被收录于专栏:大大刺猬大大刺猬
运行总次数:0
代码可运行

导读

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

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

深度分析

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

null bitmask

现象

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

代码语言:sql
复制
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值居然不一样

分析

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

简单来说就是: 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代码来表示就清楚多了

代码语言:python
代码运行次数:0
运行
复制
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查看

当存在varchar(可溢出字段)时, pack_record=1 否则pack_record=0 (8.0.28之前的checksum table的bug也是因为这玩意....)

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

验证

那我们来验证一波

代码语言:python
代码运行次数:0
运行
复制
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的值对应上了. 说明逻辑没得问题.

INT相关类型

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

现象

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

代码语言:sql
复制
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;

我们看到数据是相同的.

分析

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

代码语言:python
代码运行次数:0
运行
复制
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)

验证

然后我们来验证验证下

代码语言:python
代码运行次数:0
运行
复制
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校验的一样的.

逻辑顺序

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

我们调整下索引位置即可.

代码语言:sql
复制
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;

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

生成字段

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

现象

代码语言:sql
复制
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层做的计算)

分析

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

验证

然后我们使用python来验证下

代码语言:python
代码运行次数:0
运行
复制
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(含生成列)来计算的.

总结

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

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

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

所以影响checksum table值的因数

  1. nullable 字段能否为空 (空值不参与crc32计算,但空值的bitmask要)
  2. 字段逻辑顺序

不影响的因素

  1. 存储引擎(是server层计算的)
  2. 生成列是否虚拟(按照逻辑数据, 不考虑是否存储)
  3. unsigned 虽然有符号的1和无符号的1存储不同, 但不影响checksum
  4. 行的顺序(是所有行的crc32加起来, 所以行的顺序不影响)

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 导读
  • 深度分析
    • null bitmask
      • 现象
      • 分析
      • 验证
    • INT相关类型
      • 现象
      • 分析
      • 验证
    • 逻辑顺序
    • 生成字段
      • 现象
      • 分析
      • 验证
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档