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

2024年 5月 13日 110.7k 0

造数据

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

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

结构如下

对象 大小(字节) 描述
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
[ibd2sql] MYSQL ibd文件解析 (6)  BLOB/TEXT 页如何存储在磁盘上的 -- FIL_PAGE_TYPE_LOB_FIRST-2

于是我们可以得到, 我们这行数据有 1048576 B(1MB) 大小, 放在 7581表空间的 第5页(从0开始的异世界).
我们去数据库里面验证下.
[ibd2sql] MYSQL ibd文件解析 (6)  BLOB/TEXT 页如何存储在磁盘上的 -- FIL_PAGE_TYPE_LOB_FIRST-3
也是对得上的.

大字段页

导读就导了半天, 现在开始来看BLOB页吧. 先不看ZLIB_BLOB的.
有3种PAGE (参考:https://dev.mysql.com/blog-archive/mysql-8-0-innodb-introduces-lob-index-for-faster-updates/), 我们一个个看.
[ibd2sql] MYSQL ibd文件解析 (6)  BLOB/TEXT 页如何存储在磁盘上的 -- FIL_PAGE_TYPE_LOB_FIRST-4

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)[39:39+datalen]
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)) # 这里就不打印数据了, 太大了, 不好看.

[ibd2sql] MYSQL ibd文件解析 (6)  BLOB/TEXT 页如何存储在磁盘上的 -- FIL_PAGE_TYPE_LOB_FIRST-5
数据也不太方便校验一致性, 可以自己简单print一下看看.

题外话

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

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

相关文章

Oracle如何使用授予和撤销权限的语法和示例
Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
社区版oceanbase安装
Oracle 导出CSV工具-sqluldr2
ETL数据集成丨快速将MySQL数据迁移至Doris数据库

发布评论