[MYSQL] row_format=compressed的细节和压缩的溢出页(FIL_PAGE_TYPE_ZBLOB)的简单介绍

发布于 更新于
17

导读

我们之前讲过mysql的压缩行格式, 由于使用得不多,就没具体介绍压缩部分存储的元数据信息,也没看它的溢出页是怎么存储的. 这不恰好就有个需要这俩细节的案例.

压缩行格式

我们先来简单回顾下压缩行格式:

对象

大小

描述

FIL_HEADER+PAGE_HEADER

94

页基础信息

compressed_data

x

元数据信息和压缩的数据

uncompressed_data

y

未压缩部分的数据

…..

未使用的空间

overflow page

溢出页的记录信息, 还是每条20字节

trx_id+rollptr

13*n

事务相关信息

page diretory

2*n

page dir信息

主要是元数据信息部分上次没有讲, 而且如果元数据信息大小判断不对,就直接影响后面的数据读取.元数据信息一般都是记录字段大小是否为空之类的信息,以0x01结尾,但信息本身就可能有0x01,所以不能直接find找结束位置. 规则如下:

  1. 定长(int等)字段直接记录大小.
  2. 不超过255字节的变长字段(var)使用0x00表示.
  3. 超过255字节的变长字段使用126表示允许为空,127表示不允许为空.
  4. 最后1bit表示是否为空, 1表示为不能为空, 0表示可以为空.
  5. 连续的非空定长字段会合并.

比如int使用4字节, 则元数据计算方式为 (4<<1)|nullable 非空则为9,空为8. 比较抽象, 我们看个例子吧.

代码语言:sql
复制
-- 数据准备(注意我这里没有显示设置主键,且未重建表,所以元数据虽然在压缩部分,但数据在未压缩部分)
drop table if exists db1.t20260129_03;
create table db1.t20260129_03(c1 int,c2 int not null, c3 int not null, c4 varchar(20), c5 varchar(20) not null, c6 varchar(300), c7 varchar(300) not null, c8 blob, c9 blob not null) row_format=compressed;
insert into db1.t20260129_03 values(1,2,3,4,5,6,7,8,9);

然后我们使用python解析即可.

代码语言:python3
复制
import struct
import zlib
f = open('/data/mysql_3314/mysqldata/db1/t20260129_03.ibd','rb')
f.seek(4*8192) # 默认压缩是8K
cdata = f.read(8192)
data = zlib.decompress(cdata[94:])
print(data)
print(struct.unpack(f'>{len(data)}B',data))

于是得到如下内容:

b'rx1bx08x11x00x01~x7f~x7fx01'

(13, 27, 8, 17, 0, 1, 126, 127, 126, 127, 1)

根据上面的规则我们得到:

二进制

10进制

size计算

非空计算

结论

b'r'

13

13>>1 = 6

'Not null' if 13&1 else 'Null'

大小为6字节(rowid), 不能为空

b'x1b'

27

27>>1 = 13

'Not null' if 27&1 else 'Null'

大小为13字节(trxid+rollptr),不能为空

b'x08'

8

8>>1 = 4

'Not null' if 8&1 else 'Null'

大小为4字节(c1),可以为空

b'x11'

17

17>>1 = 8

'Not null' if 17&1 else 'Null'

大小为8字节(c2,c3),不能为空

b'x00'

0

max 255

'Not null' if 0&1 else 'Null'

不超过255字节的变长字段(c4),可以为空

b'x01'

1

max 255

'Not null' if 1&1 else 'Null'

不超过255字节的变长字段(c5),不能为空

b'~'

126

over 255

'Not null' if 126&1 else 'Null'

最大超过255字节的长字段(c6),可以为空

b'x7f'

127

over 255

'Not null' if 127&1 else 'Null'

最大超过255字节的长字段(c7),不能为空

b'~'

126

over 255

'Not null' if 126&1 else 'Null'

最大超过255字节的长字段(c8),可以为空

b'x7f'

127

over 255

'Not null' if 127&1 else 'Null'

最大超过255字节的长字段(c9),不能为空

b'x01'

1

结束标记

看起来比较复杂, 而且用处不大.

压缩行的溢出页格式 FIL_PAGE_TYPE_ZBLOB

我们之前讲过非压缩页的溢出页,5.7的也有讲, 但是没得row_format=compressed的溢出页…. 这不,就来补上了么.

其实row_format=compressed的溢出页(FIL_PAGE_TYPE_ZBLOB)非常简单, 就单纯的流式压缩,没得啥结构(除了比较固定的38字节fil_header). 大概如下图:

使用python代码表示更简单:

代码语言:python
复制
def FIRST_ZBLOB(pg,pageno):
rdata = b''
d = zlib.decompressobj()
while True:
data = pg.read(pageno)
pre,nex = struct.unpack('>LL',data[8:16])
rdata += d.decompress(data[38:])
if nex == 4294967295:
break
else:
pageno = nex
return rdata

缺点也很明显, 随便丢一个就都gg了, 而FIL_PAGE_TYPE_LOB_FIRST抽象了entry概念, 就…

测试

还是得测试才有说服力,代码已经更新了,故直接下载最新版ibd2sql即可测试:

代码语言:shell
复制
wget https://github.com/ddcw/ibd2sql/archive/refs/heads/ibd2sql-v2.x.zip
unzip ibd2sql-v2.x.zip
cd ibd2sql-ibd2sql-v2.x
python3 main.py /data/mysql_3314/mysqldata/db1/t20260129_01.ibd --ddl --sql

测试也是没得问题的. (小细节: 大字段中: lob显示为hex格式, text显示为字符形式, 也是根据mysql显示效果来的)

总结

虽然row_format=compressed使用得不多,但使用该格式就很可能有溢出页了. 简单总结下:

  1. row_format=compressed的元数据信息存储方式虽然复杂, 但我们只需要考虑不超过255字节的非空变长字段即可, 毕竟就它和结束标记符相同.
  2. row_format=compressed的溢出页比较简单, 直接按顺序解压即可. 各页之间是使用fil_header的NEXT_PAGE来关联的.
0 赞
0 收藏
分享
1 讨论
反馈
0 / 600
1 条评论
热门最新

这篇文章补上了之前MySQL压缩行格式讲解里没涉及到的细节,把压缩元数据和溢出页的存储情况都讲清楚了,还用表格清晰列出了压缩行格式各组成部分的大小与作用,对想深入了解这块内容的人很有帮助,感谢分享,期待更多这类实用的技术细节讲解~