造数据
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
结构如下
对象 | 大小(字节) | 描述 |
---|---|---|
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
于是我们可以得到, 我们这行数据有 1048576 B(1MB) 大小, 放在 7581表空间的 第5页(从0开始的异世界).
我们去数据库里面验证下.
也是对得上的.
大字段页
导读就导了半天, 现在开始来看BLOB页吧. 先不看ZLIB_BLOB的.
有3种PAGE (参考:https://dev.mysql.com/blog-archive/mysql-8-0-innodb-introduces-lob-index-for-faster-updates/), 我们一个个看.
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)) # 这里就不打印数据了, 太大了, 不好看.
数据也不太方便校验一致性, 可以自己简单print一下看看.
题外话
今天遇到某数据库 使用 ALTER TABLE ADD COLUMN XX VARCHAR(200) NULL
时, 数据库服务器负载飙到500+, 数据库一直卡着, 就离谱… 不加NULL时, 就是正常的.
ibd2sql 等下个版本(v1.4)再更新这个溢出页的功能.