导读
之前解析过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