前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >[MYSQL] frm2sdi(3) 将frm文件转为sdi page & ibd2sql v1.9 出土

[MYSQL] frm2sdi(3) 将frm文件转为sdi page & ibd2sql v1.9 出土

原创
作者头像
大大刺猬
发布2025-02-07 20:16:53
发布2025-02-07 20:16:53
24000
代码可运行
举报
文章被收录于专栏:大大刺猬大大刺猬
运行总次数:0
代码可运行

导读

终于来到frm2sdi的最后一章, 本章将如何将frm转为sdi. 前面两张分别将了frm的结构和sdi的结构. 但frm有部分元数据信息没有细看, 主要是太复杂了, 所以挪到本章来讲. 待我们将frm转为sdi之后, 我们就可以使用ibd2sql来直接解析mysql 5.7的文件了, 不在需要转到8.0里面去了.

最终效果大概如下:

decimal的0开头懒得去掉了, 毕竟这只是一个过渡版本.

frm2sdi

元数据信息

frm部分信息, 目前主要还剩metadata那17字节. 里面包含的信息非常丰富. 主要是各种精度,是否空之类的元数据信息.

对象

大小

描述

1

1

1

field_length

2

字段长度, 比如int(11), varchar(200)

recpos

3

pack_flag

2

比如是否为空.zero fill,signed

unireg_type

1

生成列, 自增列之类的信息

charset_low

1

字符集的高位

interval_nr

1

field_type

1

字段类型

collation_id

1

字符集 如果是geom的话 ,就是geom类型

comment_length

2

注释长度

前面3字节都是一些信息的一部分(毕竟1字节存不了多少, 有兴趣的可以自己研究)

我们不直接看各字段所代表的意思, 毕竟有些是多个字段合起来才能得到一个有效信息.

分区信息这种已经是字符串形式的值了, 我就不再讲了

字符串长度

对于varchar而言, 字符长度是field_length除以字符集最大字节数.(上一章讲过).

元数据里面记录的是排序规则(collate_id), 所以我们得先查询排序规则对应的字符集, 然后再查询该字符集的最大字符长度. (utf8mb3 就是utf8 most bytes 3, 所以最大字节是3 utf8mb4就是utf8 most bytes 4,所以最大字节就是4). 记不住也没事, 可以数据库里面查询.

代码语言:sql
复制
-- 查询collation_id=33的字符集
show collation where id=33;

-- 查询utf8字符集的最大字节长度
show charset where Charset='utf8';

数字精度

对于decimal的精度是来自field_length和pack_flag

numeric_precision 即字符长度(整数+小数,不含小数点), 为field_length -2

numeric_scale 为小数部分长度,是pack_flag的前1字节(注意是小端)的后4bit.代码逻辑参考:

代码语言:python
代码运行次数:0
复制
                        if col['metadata']['field_type'] == 246: # decimal
                                numeric_precision = char_length - 2
                                numeric_scale = (pack_flag>>8) & 31
                                numeric_scale_null = False

时间精度

时间字段存在精度, 比如毫秒,微秒之类的.

对于timestamp/datetime而言, datetime_precision = 字符长度 - 20

对于time而言(没得年月日), datetime_precision = 字符长度 - 11

enum和set

enum和set会在options中记录 枚举/列表的数量:interval_count.

代码语言:python
代码运行次数:0
复制
                        if field_type == 22: # enum 重新计算type_default_size
                                type_default_size = 2 if len(col['elements']) >= 2**8 else 1
                                options = f'interval_count={len(col["elements"])}'
                        elif field_type == 23: # set
                                type_default_size = (len(col['elements'])+7)//8
                                options = f'interval_count={len(col["elements"])}

默认值

默认值分为: 是否有默认值 和 默认值.

前面我们讲的默认值结构有点问题, flag部分不是定长的, 而是一个bitmask, 用来表示某个字段是否有默认值, 而不管是否有默认值, DEFAULT_VALUE部分均会保留足够大的空间(varchar(200) default 'aa', 则是b'aa'后面跟上598个b'\x00'). 对于这种使用bitmask来表示是否为空(有默认值)的方式在行存储的时候也会使用到.

如果存在pack_record, 则bitmask从第一个bit位置开始计算, 否则从第二个bit开始计算.

代码语言:python
代码运行次数:0
复制
null_bitmask_adds = 0 if self.pack_record == 1 else 1
default_value_null = False if self.default_value_null_bitmask&(1<<(i+null_bitmask_adds)) == 0 else True

对于定长类型,DEFAULT_VALUE部分直接记录值, 对于变长类型, 则会使用1-2字节记录长度, 随后记录数据, 不够的填充b'\x00'. 至于使用1/2字节则看char_length. (char_length<=255,则使用1字节). 这里的数据类型是mysql的数据类型(小端), 和innodb(大端)有区别. 计算默认值的时候, 还得考虑精度.

元数据中的"recpos-1"即表示该字段位于DEFAULT_VALUE(bitmask)的起始位置, 结束位置是下一个字段的开始位置.

是否为空

字段非空约束则是来自 pack_flag&32768

是否有符号

对于int类型还存在符号问题, 虽然我们判断char_length的值也能知道符号, 但pack_flag&1 更直接一点

是否填充0

对于int之类的还存在是否填充0,使其更美观的参数, 来自:pack_flag&4

是否自增

对于字段是否是自增的, 则看unireg_type==15

隐藏字段

mysql的表还存在trxid和rollptr的隐藏字段, 如果没得主键,还有rowid, 这部分信息在转换的时候也得考虑上. 我这里就只填充点关键信息

代码语言:python
代码运行次数:0
复制
                # 补充隐藏字段 rowid,rollptr & trxid
                if not self.HAVE_PRIMARY:
                        current_ordinal_position += 1
                        COLUMN.append({
                                'name':'DB_ROW_ID',
                                'type':10,
                                'hidden':2,
                                'char_length':6,
                                'ordinal_position':current_ordinal_position,
                        })
                current_ordinal_position += 1
                COLUMN.append({
                        'name':'DB_TRX_ID',
                        'type':10,
                        'hidden':2,
                        'char_length':6,
                        'ordinal_position':current_ordinal_position,
                })
                current_ordinal_position += 1
                COLUMN.append({
                        'name':'DB_ROLL_PTR',
                        'type':9,
                        'hidden':2,
                        'char_length':7,
                        'ordinal_position':current_ordinal_position,
                })

索引补充

如果没得主键, 就得补充主键. 构建的主键,前3个是rowid,trxid,rollptr,然后才是普通字段.

如果有主键的话, 还得再补充下trxid和rollptr,然后再补充下剩余字段(还得考虑是否是前缀索引, 我这里就先不管了....).

普通索引的话, 得补充下主键字段.

ibd2sql v1.9

我只是抽取了部分比较关键的信息讲了, 感兴趣的可以直接看ibd2sql中的相关代码. v1.9版本主要是修复一个已知BUG支持直接解析5.7的ibd文件, 即自动解析frm文件并构造一个sdi page. 还是直接瞧瞧效果吧.(用法也没变)

代码语言:shell
复制
python3 main.py /data/mysql_3314/mysqldata/ibd2sql/ddcw_alltype_table.ibd --sql --limit 1

3308实例是5.7.38环境, 3314实例是8.0.28环境.

当然提取DDL也是可以的.

代码语言:shell
复制
python3 main.py /data/mysql_3308/mysqldata/ibd2sql/ddcw_alltype_table.ibd --ddl

虽然看着比较怪, 明明是解析frm文件, 却指定的是ibd文件....

总结

frm主要是信息比较紧凑, 而且不容易扩展. sdi就是压缩的json数据(普通数据行), 长度可达4GB?

对于直接解析mysql 5.7环境的frm的前提条件: 要求同目录下存在同名的.frm文件.(虽然我去做自动识别了, 但起码得有啊)

ibd2sql v1.9下载地址: https://github.com/ddcw/ibd2sql/archive/refs/tags/v1.9.tar.gz

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 导读
  • frm2sdi
    • 元数据信息
      • 字符串长度
      • 数字精度
      • 时间精度
      • enum和set
      • 默认值
      • 是否为空
      • 是否有符号
      • 是否填充0
      • 是否自增
    • 隐藏字段
    • 索引补充
  • ibd2sql v1.9
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档