[MYSQL] mysql undo文件解析(1)

2024-08-02 12:05:57 浏览数 (1)

导读

之前解析过mysql的各种文件, 比如:ibd,redo,binlog,frm,myd. 貌似漏了个undo文件没有解析... 现在来补上 -_-

第一篇先简单看下, 尽量不涉及到具体的页解析

介绍

undo日志是要un do的时候使用的日志. 我们可以使用innodb_undo_directory控制undo日志的路径(默认在@@datadir目录下), 可以使用innodb_undo_tablespaces控制undo的文件数量.

undo里面记录的是修改前的数据(MVCC实现).每个事务最多分配4个undo日志. (主要分为insert/update(含delete)两种, 但又涉及到临时表, 所以可以看作是4种).

Each undo tablespace and the global temporary tablespace individually support a maximum of 128 rollback segments. The innodb_rollback_segments variable defines the number of rollback segments.

既然段的数量是有限的,那么支持的事务数量也就是有限的了. 官方给了个计算方式:

代码语言:txt复制
-- 如果每个事务都只有Insert或者update/delete
(innodb_page_size / 16) * innodb_rollback_segments * number of undo tablespaces = (16384/16)*128*2 = 262144

-- 如果每个事务都有insert和update/delete
(innodb_page_size / 16 / 2) * innodb_rollback_segments * number of undo tablespaces = (16384/16/2)*128*2 = 131072

-- 如果每个事务都是对临时表做insert (不存在只update/insert临时表)
(innodb_page_size / 16) * innodb_rollback_segments = (16384/16)*128 = 131072

-- 如果每个事务都是对临时表做insert和update/delete
(innodb_page_size / 16 / 2) * innodb_rollback_segments = (16384/16/2)*128 = 65536

看起来支持的事务数量是有限的, 但一般达不到这么多的事务量, 所以也不用关心这个.

按65536看, 那都得至少65536个连接在跑事务

undo_001

扯远了, 我们还是来看看undo的文件结构吧

我这是两个undo文件, 均为16MB(实际上可能某一个会大很多). undo也是innodb实现的,那么应该和ibd文件之类的格式类似, 我们使用如下python代码解析下:

代码语言:python代码运行次数:0复制
import struct
filename = '/data/mysql_dev/data/undo_001'
f = open(filename,'rb')
linesize = 20
f.seek(0,0)
for i in range(int(os.stat(filename).st_size/16384)):
	bdata = f.read(16384)
	FIL_PAGE_SPACE_OR_CHKSUM, FIL_PAGE_OFFSET, FIL_PAGE_PREV, FIL_PAGE_NEXT, FIL_PAGE_LSN, FIL_PAGE_TYPE, FIL_PAGE_FILE_FLUSH_LSN, FIL_PAGE_SPACE_ID = struct.unpack('>4LQHQL',bdata[:38])
	print(f'{FIL_PAGE_TYPE} ',end = 'n' if i%linesize == 0 else '')

可以看到

第一页是8(FIL_PAGE_TYPE_FSP_HDR),

第二页是5(FIL_PAGE_IBUF_BITMAP),

第三页是3(FIL_PAGE_INODE) -- ibd文件记录索引段的

和ibd文件是一样的.

第4页是21(FIL_PAGE_TYPE_RSEG_ARRAY) 是undo特有的页(Rollback Segment Array page )

剩下的都是 6(FIL_PAGE_TYPE_SYS) 2(FIL_PAGE_UNDO_LOG)和0(FIL_PAGE_TYPE_ALLOCATED)了.

也就是我们只需要再了解FIL_PAGE_TYPE_RSEG_ARRAY,FIL_PAGE_TYPE_SYS和FIL_PAGE_UNDO_LOG 就可以解析undo文件了.

先不急, 我们利用下之前的工具ibd2sql的debug功能来获取下rollptr.

我们先准备下测试表吧

代码语言:sql复制
create table t20240802(id int, name varchar(200));
insert into t20240802 values(1,'ddcw');
insert into t20240802 values(2,'ddcw'); 
insert into t20240802 values(3,'ddcw');
update t20240802 set name='newddcw' where id=2;

然后使用ibd2sql解析这行被修改的数据

代码语言:shell复制
python3 main.py /data/mysql_dev/data/db1/t20240802.ibd --sql --debug

这个回滚指针roll pointer的格式为:

对象

大小

描述

offset

2字节

在page中的位置

page_no

4字节

在哪个页面

rseg_id

7bit

rollback segment ID

is_insert

1bit

是否为insert

所以我们可以使用如下python代码解析回滚指针

代码语言:python代码运行次数:0复制
rolll_ptr = 562949973550095
offset = rolll_ptr & 0xFFFF
page_no = (rolll_ptr>>16) & 0xFFFFFFFF
rseg_id = (rolll_ptr>>48) & 0x7F
is_insert = True if rolll_ptr>>55 == 1 else False
print(f"PAGENO:{page_no}  OFFSET:{offset}  rseg_id:{rseg_id}  is_insert:{is_insert}")

然后我们去undo里面对应的位置解析瞧瞧

代码语言:python代码运行次数:0复制
import struct
filename = '/data/mysql_dev/data/undo_002'
f = open(filename,'rb')
f.seek(page_no*16384,0)
data = f.read(16384)
data[offset:offset 100]

这个record格式前两字节为下一record位置(绝对), 最后2字节为上一record位置(绝对)

保存的数据是使用lv格式的, 即长度 数据(后面再讲吧. 这里我们就已经看到我们删除前的数据了).

参考:

https://dev.mysql.com/doc/refman/8.0/en/innodb-undo-logs.html

https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html

https://github.com/mysql/mysql-server/blob/trunk/storage/innobase

0 人点赞