mysql 8.0的系统表是在mysql.ibd文件中,记录内容非常全(基本上和ibd中的sdi差不多),可以拼接成实际的DDL.

mysql 5.7的系统表是在ibdata1中的, 是否也能拼接为真实的DDL呢?
先来看看ibdata1中的各系统表的表结构,这部分信息只有自己拼接(8.0是有专门的sdi page来记录的). 经过一顿拼接后得到如下定义:
-- mysql 5.7的系统表DDL
-- 参考
-- dict/dict0crea.cc
-- include/dict0boot.h
-- dict/dict0boot.cc
-- dict/dict0dict.cc
-- ...
-- index_id 1
drop table if exists sys_tables;
create table sys_tables(
name varchar(654),
id bigint unsigned,
n_cols int,
`type` int unsigned,
mix_id bigint unsigned,
mix_len int unsigned,
cluster_name varchar(255),
space int unsigned,
primary key(name)
) engine=innodb row_format=redundant charset=latin1;
-- index_id 2
drop table if exists sys_columns;
create table sys_columns(
table_id bigint unsigned,
pos int unsigned,
name varchar(255),
mtype int unsigned, -- main data type
prtype int unsigned, -- precise type: charset(16 bit),nullable,signed...
len int unsigned,
prec int unsigned,
primary key(table_id,pos)
) engine=innodb row_format=redundant;
-- index_id 3
drop table if exists sys_indexes;
create table sys_indexes(
table_id bigint unsigned,
id bigint unsigned, -- indexid
name varchar(255),
n_fields int unsigned,
`type` int unsigned,
space int unsigned,
page_no int unsigned,
merge_threshold int unsigned,
primary key(table_id,id)
) engine=innodb row_format=redundant;
-- index_id 4
drop table if exists sys_fields;
create table sys_fields(
index_id bigint unsigned,
pos int unsigned,
col_name varchar(255),
primary key(index_id,pos)
) engine=innodb row_format=redundant;
-- index_id 11
drop table if exists sys_foreign;
create table sys_foreign(
name varchar(255),
for_name varchar(255),
ref_name varchar(255),
n_cols int unsigned,
primary key(name), -- index_id 11
key(for_name), -- index_id 12
key(ref_name) -- index_id 13
) engine=innodb row_format=redundant;
-- index_id 14
drop table if exists sys_foreign_cols;
create table sys_foreign_cols(
name varchar(255),
pos int unsigned,
for_col_name varchar(255),
ref_col_name varchar(255),
primary key(name, pos)
) engine=innodb row_format=redundant;
-- index_id 15
drop table if exists sys_tablespaces;
create table sys_tablespaces(
space int unsigned, -- index_id
name varchar(255),
flags int unsigned,
primary key(space)
) engine=innodb row_format=redundant;
-- index_id 16
drop table if exists sys_datafiles;
create table sys_datafiles(
space int unsigned, -- index_id
path varchar(255),
primary key(space)
) engine=innodb row_format=redundant;
-- index_id 17
drop table if exists sys_virtual;
create table sys_virtual(
table_id bigint unsigned,
pos int unsigned,
base_pos int unsigned,
primary key(table_id,pos,base_pos)
) engine=innodb row_format=redundant;看起来比较多, 但实际上主要是前面4张系统表. 我们一个个来看.
有了系统表表结构之后,就可以使用ibd2sql来解析了.有兴趣的可以自己试试.
这个呢,主要就是记录表结构的, 主键是表名(这就比较离谱),通常使用table_id和其它系统表关联. 没啥好看的,就那样.
表名, 严格来说是: dbname/tablename
就是table_id, 其它信息都是通过table_id关联的
前2字节表示有多少个虚拟列, 后2个字节表示有多少非虚拟列.
第1bit表示是否是compact的, 0表示是redundant
一些flag,和FSP中的flags很像.
/*
DICT_TF_WIDTH_ZIP_SSIZE = 4
DICT_TF_POS_COMPACT = 0
DICT_TF_WIDTH_COMPACT = 1
DICT_TF_POS_ZIP_SSIZE = (DICT_TF_POS_COMPACT + DICT_TF_WIDTH_COMPACT)
DICT_TF_MASK_ZIP_SSIZE = ((~(~0 << DICT_TF_WIDTH_ZIP_SSIZE)) << DICT_TF_POS_ZIP_SSIZE)
DICT_TF_WIDTH_ATOMIC_BLOBS = 1
DICT_TF_POS_ATOMIC_BLOBS = (DICT_TF_POS_ZIP_SSIZE + DICT_TF_WIDTH_ZIP_SSIZE)
DICT_TF_MASK_ATOMIC_BLOBS = ((~(~0 << DICT_TF_WIDTH_ATOMIC_BLOBS)) << DICT_TF_POS_ATOMIC_BLOBS)
DICT_TF_WIDTH_DATA_DIR = 1
DICT_TF_POS_DATA_DIR = (DICT_TF_POS_ATOMIC_BLOBS + DICT_TF_WIDTH_ATOMIC_BLOBS)
DICT_TF_MASK_DATA_DIR = ((~(~0 << DICT_TF_WIDTH_DATA_DIR)) << DICT_TF_POS_DATA_DIR)
DICT_TF_WIDTH_SHARED_SPACE = 1
DICT_TF_POS_SHARED_SPACE = (DICT_TF_POS_DATA_DIR + DICT_TF_WIDTH_DATA_DIR)
DICT_TF_MASK_SHARED_SPACE = ((~(~0 << DICT_TF_WIDTH_SHARED_SPACE)) << DICT_TF_POS_SHARED_SPACE)
*/
DICT_TF_MASK_ZIP_SSIZE = 30;
DICT_TF_MASK_ATOMIC_BLOBS = 32;
DICT_TF_MASK_DATA_DIR = 64;
DICT_TF_MASK_SHARED_SPACE = 128;
type = 1;
type |= flags & (DICT_TF_MASK_ZIP_SSIZE
| DICT_TF_MASK_ATOMIC_BLOBS
| DICT_TF_MASK_DATA_DIR
| DICT_TF_MASK_SHARED_SPACE);还有些row_format之类的信息.
如果n_cols的第1bit(&2**31)是0表示是REDUNDANT, 否则可使用如下规则判断:
def GET_ROWFORMT_FROM_TABLE_FLAGS(flags):
if not flags&1: # 第1bit固定是1, 所以这个永远用不到.
return 'REDUNDANT'
elif not (flags&32)>>5:
return 'COMPACT'
elif (flags&30)>>1:
return 'COMPRESSED'
else:
return 'DYNAMIC'不造, 貌似都是0
table的另一部分flag, 就使用了9bit,如下:
对象 | 大小 | 描述 |
|---|---|---|
23 bit | ||
DICT_TF2_ENCRYPTION | 1 bit | 是否加密, flag:256 |
DICT_TF2_INTRINSIC | 1 bit | 内部表, flag:128 |
DICT_TF2_FTS_AUX_HEX_NAME | 1 bit | fts使用16进制, flag:64 |
DICT_TF2_DISCARDED | 1 bit | 表空间discard了, flag:32 |
DICT_TF2_USE_FILE_PER_TABLE | 1 bit | 独立表空间文件, flag:16 |
DICT_TF2_FTS_ADD_DOC_ID | 1 bit | fts添加doc id, flag:8 |
DICT_TF2_FTS | 1 bit | 是否有fts, flag:4 |
DICT_TF2_FTS_HAS_DOC_ID | 1 bit | 是否有doc id, flag:2 |
DICT_TF2_TEMPORARY | 1 bit | 临时表, flag:1 |
通常是80, 也就是使用独立表空间文件
集群名字,貌似不支持, 全都是null的
表空间id,和sys_tablespaces,sys_datafiles管理的, 其实就是主键索引的index_id, 也是本次恢复drop表需要的关键信息之一.
这是记录表字段信息的表, 解析不到位就无法获取正确的DDL,也就无法解析数据了; 所以我们仔细瞅瞅:
和表关联起来
就是sdi里面的ordinal_position, 但是不包含虚拟列的(包含stored的), 虚拟列的pos非常大, 比如131118,65573之类的
那么有virtual列的怎么计算呢? 请看:
/* 创建虚拟列 */
dict_create_v_col_pos(
ulint v_pos,
ulint col_pos)
{
ut_ad(v_pos <= REC_MAX_N_FIELDS);
ut_ad(col_pos <= REC_MAX_N_FIELDS);
return(((v_pos + 1) << 16) + col_pos);
}
/*获取虚拟列的虚拟pos, 也就是第几个虚拟列*/
dict_get_v_col_pos(
ulint pos)
{
return((pos >> 16) - 1);
}
/*获取虚拟列的真实pos, 其它字段都pos之后,再把虚拟列插进去*/
ulint
dict_get_v_col_mysql_pos(
ulint pos)
{
return(pos & 0xFFFF);
}也就是前2字节表示是第几个虚拟列, 后2字节表示该虚拟列在表的真实位置.
字段名
主要的数据类型: main data type
不是那么具体, 具体如下:
mtype值 | 类型 |
|---|---|
0 | DATA_MISSING |
1 | DATA_VARCHAR |
2 | DATA_CHAR |
3 | DATA_FIXBINARY |
4 | DATA_BINARY |
5 | DATA_BLOB |
6 | DATA_INT |
7 | DATA_SYS_CHILD |
8 | DATA_SYS |
9 | DATA_FLOAT |
10 | DATA_DOUBLE |
11 | DATA_DECIMAL |
12 | DATA_VARMYSQL |
13 | DATA_MYSQL |
14 | DATA_GEOMETRY |
15 | DATA_POINT |
16 | DATA_VAR_POINT |
63 | DATA_MTYPE_MAX |
对于enum是没有具体的真实值的, 只有大小和int值,能用,但不完全能用
精度相关的:precise type, 类容相当丰富:字符集,是否为空,有无符号等. 详情如下:
对象 | 大小 | 描述 |
|---|---|---|
COLLATION_ID | 16 bit | 字符集id |
2 bit | ||
DATA_VIRTUAL | 1 bit | 是否是虚拟字段, flag:8192 |
DATA_LONG_TRUE_VARCHAR | 1 bit | 是否是varchar, flag:4096 |
DATA_GIS_MBR | 1 bit | 是否是gis的, flag:2048 (通常看mtype) |
DATA_BINARY_TYPE | 1 bit | 是否是二进制的,flag:1024 |
DATA_UNSIGNED | 1 bit | 是否有符号, flag:512 |
DATA_NOT_NULL | 1 bit | 是否为空, flag:256 |
DATA_MYSQL_TYPE_MASK | 8 bit | mysql数据类型,flag:255 |
字符/字节长度
生成的虚拟列使用了几个字段.
`gen_virtual` int(11) GENERATED ALWAYS AS ((`id_default` + 1)) VIRTUAL, -- prec=1
`gen_stored` int(11) GENERATED ALWAYS AS ((`int_col` + 1)) STORED, -- prec=0 stored是存储在磁盘上的
`concat_char` varchar(201) GENERATED ALWAYS AS (concat(convert(`char_col` using utf8mb4),' ',`varchar_col`)) VIRTUAL, -- prec=2记录索引信息的. 含全文索引
表id,和sys_tables关联的
索引id, 标识索引的,和sys_fields(索引字段详情)关联
索引名字
这个索引有几个字段
索引类型, 每个bit都有不同的表示. 虽然存储了4字节, 但只使用1字节.
对象 | 大小 | 描述 |
|---|---|---|
DICT_CLUSTERED | 1 bit | 主键, flag:1 |
DICT_UNIQUE | 1 bit | 唯一索引, flag:2 |
1bit | ||
DICT_IBUF | 1 bit | insert buffer tree, flag:8 |
DICT_CORRUPT | 1 bit | 异常的. flag:16 |
DICT_FTS | 1 bit | 全文索引, flag:32 |
DICT_SPATIAL | 1 bit | 空间索引. flag:64 |
DICT_VIRTUAL | 1 bit | 索引字段是虚拟字段, flag:128 |
比如主键, 既是主键,又是唯一的, 所以type=3
就是index_id, 主键的该值和sys_tables中的是一样的
估计是想存储root page no,但实际上未使用, 都是4294967295.
合并相关的, 页使用低到这个值(百分比)就合并. 默认是50(最大也是这么大), 可以建表的时候修改
(root@127.0.0.1) [(none)]> create table db1.t20251231_01(id int,name varchar(200)) comment='MERGE_THRESHOLD=40';
Query OK, 0 rows affected (0.01 sec)
(root@127.0.0.1) [(none)]> show create table db1.t20251231_01\G
*************************** 1. row ***************************
Table: t20251231_01
Create Table: CREATE TABLE `t20251231_01` (
`id` int(11) DEFAULT NULL,
`name` varchar(200) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='MERGE_THRESHOLD=40'
1 row in set (0.00 sec)
[root@ddcw21 ibd2sql-ibd2sql-v2.x]#python3 main.py /data/mysql_5744/mysqldata/ibdata1 --scan --table=sys_tables| grep t20251231_01
PAGENO: 395 INSERT INTO `db1`.`sys_tables` VALUES ('db1/t20251231_01',20145,2,33,0,80,null,20163)
[root@ddcw21 ibd2sql-ibd2sql-v2.x]#python3 main.py /data/mysql_5744/mysqldata/ibdata1 --scan --table=sys_indexes | grep 20145
PAGENO: 1034 INSERT INTO `db1`.`sys_indexes` VALUES (20145,20196,'GEN_CLUST_INDEX',0,1,20163,4294967295,40)写法看起来是注释, 实际上不只是注释, 当然也可以指定具体的索引的合并值:
CREATE TABLE t1 (
id INT,
KEY id_index (id) COMMENT 'MERGE_THRESHOLD=40'
);sys_indexes只是索引基础信息, 没有具体的字段信息, 这个sys_fields就是来记录具体信息的.
和哪个索引关联的
这个字段在索引的哪个位置和前缀长度.
前2字节表示在索引中的位置; 后2字节表示前缀长度(字节). 也就是sdi里面的column_opx和length.
字段名字
上面那4张表是必须的, 后面这些外键之类的就不那么重要,点到即止. 起码对数据恢复没用.
外键名字, 格式: schema/name
外表的表名,格式 schema/table
依赖的表的表名,格式 schema/table
number of indexes' first fields for which the foreign key constraint is defined: we allow the indexes to contain more fields than mentioned in the constraint, as long as the first fields are as mentioned.
外键的具体字段
外键名字
pos
外键字段名
依赖的表的字段名
index_id
表空间名字, 格式:schema/tablename
同sys_tables的type
index_id
数据文件的名字
虚拟字段相关的
同sys_columns的table_id
同sys_columns的pos
sys_columns还没有记录这个虚拟列由哪些字段组成, 这个base_pos就是那些字段.(从0开始计数的). 但是不知道虚拟字段的关系啊, 光有这玩意有屁用....
看起来这些信息非常之多,数据类型,数据大小,非空,符号等都有, 从磁盘读取数据应该是没啥问题了;
ibdata1中的系统表记录的都是存取数据所必须的数据,至于取出来之后能否"读得懂"就不关心了, 所以我们无法直接从ibdata1中的系统表提取DDL, 当然,如果字段"简单"的话,还是可以的.
能,但不完全能.
本来准备从系统表里面拼接被drop表的DDL的, 但5.7的环境不太配合; 当然通常开发侧都能提供相关DDL的,不能的话就只能看运气了.
参考:
https://dev.mysql.com/doc/refman/5.7/en/index-page-merge-threshold.html
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。