终于来到frm2sdi的最后一章, 本章将如何将frm转为sdi. 前面两张分别将了frm的结构和sdi的结构. 但frm有部分元数据信息没有细看, 主要是太复杂了, 所以挪到本章来讲. 待我们将frm转为sdi之后, 我们就可以使用ibd2sql来直接解析mysql 5.7的文件了, 不在需要转到8.0里面去了.
最终效果大概如下:
decimal的0开头懒得去掉了, 毕竟这只是一个过渡版本.
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). 记不住也没事, 可以数据库里面查询.
-- 查询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.代码逻辑参考:
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会在options中记录 枚举/列表的数量:interval_count.
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开始计算.
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 更直接一点
对于int之类的还存在是否填充0,使其更美观的参数, 来自:pack_flag&4
对于字段是否是自增的, 则看unireg_type==15
mysql的表还存在trxid和rollptr的隐藏字段, 如果没得主键,还有rowid, 这部分信息在转换的时候也得考虑上. 我这里就只填充点关键信息
# 补充隐藏字段 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版本主要是修复一个已知BUG和支持直接解析5.7的ibd文件, 即自动解析frm文件并构造一个sdi page. 还是直接瞧瞧效果吧.(用法也没变)
python3 main.py /data/mysql_3314/mysqldata/ibd2sql/ddcw_alltype_table.ibd --sql --limit 1
3308实例是5.7.38环境, 3314实例是8.0.28环境.
当然提取DDL也是可以的.
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 删除。