首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >[MYSQL] 5.7能否从ibdata1中提取出表DDL

[MYSQL] 5.7能否从ibdata1中提取出表DDL

原创
作者头像
大大刺猬
发布2025-12-31 19:13:58
发布2025-12-31 19:13:58
1740
举报
文章被收录于专栏:大大刺猬大大刺猬

导读

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

mysql 5.7的系统表是在ibdata1中的, 是否也能拼接为真实的DDL呢?

ibdata1中系统表解析

先来看看ibdata1中的各系统表的表结构,这部分信息只有自己拼接(8.0是有专门的sdi page来记录的). 经过一顿拼接后得到如下定义:

代码语言:sql
复制
-- 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来解析了.有兴趣的可以自己试试.

sys_tables

这个呢,主要就是记录表结构的, 主键是表名(这就比较离谱),通常使用table_id和其它系统表关联. 没啥好看的,就那样.

name

表名, 严格来说是: dbname/tablename

id

就是table_id, 其它信息都是通过table_id关联的

n_cols

前2字节表示有多少个虚拟列, 后2个字节表示有多少非虚拟列.

第1bit表示是否是compact的, 0表示是redundant

type

一些flag,和FSP中的flags很像.

代码语言:c++
复制
/*
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, 否则可使用如下规则判断:

代码语言:python
复制
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'

mix_id

不造, 貌似都是0

mix_len

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, 也就是使用独立表空间文件

cluster_name

集群名字,貌似不支持, 全都是null的

space

表空间id,和sys_tablespaces,sys_datafiles管理的, 其实就是主键索引的index_id, 也是本次恢复drop表需要的关键信息之一.

sys_columns

这是记录表字段信息的表, 解析不到位就无法获取正确的DDL,也就无法解析数据了; 所以我们仔细瞅瞅:

table_id

和表关联起来

pos

就是sdi里面的ordinal_position, 但是不包含虚拟列的(包含stored的), 虚拟列的pos非常大, 比如131118,65573之类的

那么有virtual列的怎么计算呢? 请看:

代码语言:c++
复制
/* 创建虚拟列 */
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字节表示该虚拟列在表的真实位置.

name

字段名

mtype

主要的数据类型: 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值,能用,但不完全能用

prtype

精度相关的: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

len

字符/字节长度

prec

生成的虚拟列使用了几个字段.

代码语言:sql
复制
`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

sys_indexes

记录索引信息的. 含全文索引

table_id

表id,和sys_tables关联的

id

索引id, 标识索引的,和sys_fields(索引字段详情)关联

name

索引名字

n_fields

这个索引有几个字段

type

索引类型, 每个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

space

就是index_id, 主键的该值和sys_tables中的是一样的

page_no

估计是想存储root page no,但实际上未使用, 都是4294967295.

merge_threshold

合并相关的, 页使用低到这个值(百分比)就合并. 默认是50(最大也是这么大), 可以建表的时候修改

代码语言:sql
复制
(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)

写法看起来是注释, 实际上不只是注释, 当然也可以指定具体的索引的合并值:

代码语言:sql
复制
CREATE TABLE t1 (
   id INT,
  KEY id_index (id) COMMENT 'MERGE_THRESHOLD=40'
);

sys_fields

sys_indexes只是索引基础信息, 没有具体的字段信息, 这个sys_fields就是来记录具体信息的.

index_id

和哪个索引关联的

pos

这个字段在索引的哪个位置和前缀长度.

前2字节表示在索引中的位置; 后2字节表示前缀长度(字节). 也就是sdi里面的column_opx和length.

col_name

字段名字

sys_foreign

上面那4张表是必须的, 后面这些外键之类的就不那么重要,点到即止. 起码对数据恢复没用.

name

外键名字, 格式: schema/name

for_name

外表的表名,格式 schema/table

ref_name

依赖的表的表名,格式 schema/table

n_cols

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.

sys_foreign_cols

外键的具体字段

name

外键名字

pos

pos

for_col_name

外键字段名

ref_col_name

依赖的表的字段名

sys_tablespaces

space

index_id

name

表空间名字, 格式:schema/tablename

flags

同sys_tables的type

sys_datafiles

space

index_id

path

数据文件的名字

sys_virtual

虚拟字段相关的

table_id

同sys_columns的table_id

pos

同sys_columns的pos

base_pos

sys_columns还没有记录这个虚拟列由哪些字段组成, 这个base_pos就是那些字段.(从0开始计数的). 但是不知道虚拟字段的关系啊, 光有这玩意有屁用....

能否从ibdata1中提取出表DDL?

看起来这些信息非常之多,数据类型,数据大小,非空,符号等都有, 从磁盘读取数据应该是没啥问题了;

  1. 但set和enum的真实值呢? 没有!, 毕竟存储的是类似int类型的值,也就不需要了.
  2. 那么decimal(m,n)的整数/小数部分值呢? 没有, 毕竟存取只需要最终的len即可.
  3. 自增呢?

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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 导读
  • ibdata1中系统表解析
    • sys_tables
      • name
      • id
      • n_cols
      • type
      • mix_id
      • mix_len
      • cluster_name
      • space
    • sys_columns
      • table_id
      • pos
      • name
      • mtype
      • prtype
      • len
      • prec
    • sys_indexes
      • table_id
      • id
      • name
      • n_fields
      • type
      • space
      • page_no
      • merge_threshold
    • sys_fields
      • index_id
      • pos
      • col_name
    • sys_foreign
      • name
      • for_name
      • ref_name
      • n_cols
    • sys_foreign_cols
      • name
      • pos
      • for_col_name
      • ref_col_name
    • sys_tablespaces
      • space
      • name
      • flags
    • sys_datafiles
      • space
      • path
    • sys_virtual
      • table_id
      • pos
      • base_pos
  • 能否从ibdata1中提取出表DDL?
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档