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

[ibd2sql] MYSQL ibd文件解析 (6) BLOB/TEXT 页如何存储在磁盘上的 -- FIL_PAGE_TYPE_LOB_FIRST

原创 大大刺猬 2024-05-13
457

造数据

create table ibd2sql.t20240513_extrapage(id int, aa longblob); insert into ibd2sql.t20240513_extrapage values(1,repeat('x', 1024*1024));

导读

虽然ibd2sql已经支持了 大字段(BLOB), 但还不支持溢出页(extra page), 也就是对大字段支持不完全. 是时候表演正在的技术了 是时候来完善大字段溢出页了.

我们知道, 如果 BLOB 字段太大, 是存储在 FIL_PAGE_TYPE_LOB_DATA 里面的. FIL_PAGE_INDEX 只存储20字节基础信息. 可以使用 ibd2sql --debug 来查看这20字节是的具体内容

python main.py /data/mysql_3314/mysqldata/ibd2sql/t20240513_extrapage.ibd --sql --debug

image.png

结构如下

对象 大小(字节) 描述
SPACE_ID 4 表空间ID
PAGENO 4 表空间里的页号
BLOB_HEADER 4 BLOB_HEADER的大小, 固定 为 1
REAL_SIZE 8 这行数据中这个字段的大小
>>> data = b'\x00\x00\x1e\xab\x00\x00\x00\x05\x00\x00\x00\x01\x00\x00\x00\x00\x00\x10\x00\x00' >>> import struct >>> struct.unpack('>3LQ',data) (7851, 5, 1, 1048576) >>>

REAL_SIZE: The 2 highest bits are reserved to the flags below
image.png

于是我们可以得到, 我们这行数据有 1048576 B(1MB) 大小, 放在 7581表空间的 第5页(从0开始的异世界).
我们去数据库里面验证下.
image.png
也是对得上的.

大字段页

导读就导了半天, 现在开始来看BLOB页吧. 先不看ZLIB_BLOB的.
有3种PAGE (参考:https://dev.mysql.com/blog-archive/mysql-8-0-innodb-introduces-lob-index-for-faster-updates/), 我们一个个看.
image.png

FIL_PAGE_TYPE_LOB_FIRST

看名字就知道这是第一页, 也就是INDEX_PAGE里面那20字节种的PAGENO. 主要存储一些基础信息(BLOB_INDEX), 剩下的空间用来存BLOB数据. 结构如下: (不看FILE_TRAILER了) 参考: storage/innobase/include/lob0first.h

对象 大小(字节) 描述
FIL_PAGE_DATA 38 FILE头, PAGE都有的那玩意.之前讲过
OFFSET_VERSION 1 版本,为1
OFFSET_FLAGS 1 flag, 目前就用了1bit,先不用管
OFFSET_LOB_VERSION 4 BLOB版本
OFFSET_LAST_TRX_ID 6 最新修改的事务ID
OFFSET_LAST_UNDO_NO 4 对应的undo no
OFFSET_DATA_LEN 4 数据大小
OFFSET_TRX_ID 6 创建时的事务ID
OFFSET_INDEX_LIST FLST_BASE_NODE_SIZE(16) INDEX信息,
OFFSET_INDEX_FREE_NODES LST_BASE_NODE_SIZE(16) 空闲的entry(就是羡慕的LOB_PAGE_DATA),
LOB_PAGE_DATA 10*index_entry_t=600 index信息, 第一页只放10个, 不够再由LOB_INDEX来放
DATA n 剩余的空间可以用来放数据

FLST_BASE_NODE_SIZE 这种结构, 之前讲过, 就是 4+6+6 也就是 记录
LEN, PRE_PAGENO,PRE_OFFSET NEXT_PAGENO, NEXT_OFFSET. 如果是0/4294967295就表示没得上/下节点了. 还是整个表吧…

对象 大小(bytes) 描述
LEN 4 数据大小
PRE_PAGENO 4 上一节点(LOB_INDEX)的页号
PRE_OFFSET 2 上一节点的页内偏移量
NEXT_PAGENO 4 下一节点的页号
NEXT_OFFSET 2 下一节点的页内偏移量

再来看看这个 LOB_PAGE_DATA, 就是ENTRY, 每个60字节, 第一页10个constexpr static ulint node_count() {return (10);} 参考: storage/innobase/include/lob0index.h :: index_entry_t
这里面就是记录 实际的值了, 全部加起来就是这行数据这个字段的 值了. 直接上表:
ENTRY:

对象 大小 描述
OFFSET_PREV FIL_ADDR_SIZE(6) 上一个entry的信息
OFFSET_NEXT FIL_ADDR_SIZE(6) 下一个entry的信息
OFFSET_VERSIONS FLST_BASE_NODE_SIZE(16) 大小, 起止entry信息
OFFSET_TRXID 6 创建时的事务ID
OFFSET_TRXID_MODIFIER 6 修改时的事务ID
OFFSET_TRX_UNDO_NO 4 创建时事务时候的UNDO NO
OFFSET_TRX_UNDO_NO_MODIFIER 4 修改时事务时候的UNDO NO
OFFSET_PAGE_NO 4 PAGE NO (LOB_DATA)
OFFSET_DATA_LEN 4 大小(实际上就前2个字节)
OFFSET_LOB_VERSION 4 LOB VERSION

OFFSET_PAGE_NO 就是指的LOB DATA的页号, OFFSET_DATA_LEN 就是lOB DATA页里面存储的数据大小(虽然是4字节, 实际只使用2字节).

FIL_PAGE_TYPE_LOB_INDEX

记录索引信息, 就是ENTRY, 比较简单.结构如下:
参考: storage/innobase/include/lob0index.h

对象 大小 描述
FIL_PAGE_DATA 38 FIL_PAGE_DATA
OFFSET_VERSION 1 LOB VERSION
OFFSET_DATA_LEN 4 数据长度
OFFSET_TRX_ID 6 事务ID
LOB_PAGE_DATA entry 一个个entry, 每个60字节, 结构见上面的

FIL_PAGE_TYPE_LOB_DATA

存放LOB数据的, 结构更简单, 就是 FIL_PAGE_DATA + OFFSET_VERSION =39 剩下的全是数据. 大小在entry里面记录的. 只管嗷嗷读就行.

测试

对应我们解析ibd文件来说, 使用到的信息不多, 所以我就只读entry了, 反正是链表.

import struct firstpagno = 5 filename = "/data/mysql_3314/mysqldata/ibd2sql/t20240513_extrapage.ibd" f = open(filename,'rb') # 二进制只读 f.seek(firstpagno*16384,0) # 移动到指定的PAGENO data = f.read(16384) entry = data[96:96+60] def read_page(pageno): f.seek(pageno*16384) return f.read(16384) rdata = b'' while True: pageno,datalen,lobversion = struct.unpack('>3L',entry[-12:]) datalen = datalen>>16 if pageno == firstpagno: rdata += data[696:696+datalen] else: rdata += read_page(pageno)[49:49+datalen] # 之前忘了加 OFFSET_DATA_LEN 和 OFFSET_TRX_ID (10字节)了... next_entry = struct.unpack('>LH',entry[6:12]) if next_entry[0] >0 and next_entry[0] < 4294967295: entry = read_page(next_entry[0])[next_entry[1]:next_entry[1]+60] else: break print(len(rdata)) # 这里就不打印数据了, 太大了, 不好看. import hashlib print(hashlib.sha1(rdata).hexdigest())

image.png
~~数据也不太方便校验一致性, 可以自己简单print一下看看. ~~
可以使用hash校验

select sha1(aa) from `ibd2sql`.`t20240513_extrapage`;

题外话

今天遇到某数据库 使用 ALTER TABLE ADD COLUMN XX VARCHAR(200) NULL 时, 数据库服务器负载飙到500+, 数据库一直卡着, 就离谱… 不加NULL时, 就是正常的.

ibd2sql 等下个版本(v1.4)再更新这个溢出页的功能.

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

评论