前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[ibd2sql] MYSQL ibd文件解析 (6) BLOB/TEXT 页如何存储在磁盘上的 -- FIL_PAGE_TYPE_LOB_FIRST

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

原创
作者头像
大大刺猬
发布2024-05-13 17:40:28
1560
发布2024-05-13 17:40:28
举报
文章被收录于专栏:大大刺猬大大刺猬

造数据

代码语言:sql
复制
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字节是的具体内容

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

这行数据中这个字段的大小

代码语言:python
代码运行次数:0
复制
>>> 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了, 反正是链表.

代码语言:python
代码运行次数:0
复制
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)再更新这个溢出页的功能.

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 造数据
  • 导读
  • 大字段页
    • FIL_PAGE_TYPE_LOB_FIRST
      • FIL_PAGE_TYPE_LOB_INDEX
        • FIL_PAGE_TYPE_LOB_DATA
        • 测试
        • 题外话
        相关产品与服务
        云数据库 MySQL
        腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档