InnoDB文件物理结构解析7 - FIL_PAGE_SDI

在数据库系统中,通常包含数据字典(data dictionary)用于记录数据库对象的元数据(表,分区,触发器,存储过程,函数的定义),我们可以通过information_schema(i_s)数据库下的视图(view)或者SHOW语句来访问数据字典。例如,可以通过show create table sakila.film语句获取film表的DDL语句。或者使用的innodb_indexes, innodb_tables,… 视图获取表元数据信息。 在MySQL 8.0之前的版本,数据字典信息存储在基于文件的元数据文件,不支持事务的表,和存储引擎相关的数据字典中。

在MySQL 5.7版本,sakila.film表,除了film.ibd文件,还有film.frm和film.TRG文件。.TRG文件保存了film表相关的触发器元数据,而.frm文件则存储了film表的元数据,包括字段名,字段数据类型等信息。基于文件的数据字典不支持事务,在管理上有一系列的问题,因此从MySQL8.0版本开始,MySQL引入一个基于事务的数据字典来存储数据对象的信息。详细请参考:
https://minervadb.com/index.php/2018/06/11/mysql-8-0-data-dictionary/
https://dev.mysql.com/doc/refman/8.0/en/data-dictionary.html

Data dictionary tables are created in a single InnoDB tablespace named mysql.ibd, which resides in the MySQL data directory. The mysql.ibd tablespace file must reside in the MySQL data directory and its name cannot be modified or used by another tablespace.

注意:是不允许通过SQL语句直接访问数据字典的:

root@localhost [testcase]> select * from mysql.tables;
ERROR 3554 (HY000): Access to data dictionary table ‘mysql.tables’ is rejected.

但可以通过一些方法绕过限制: https://lefred.be/content/mysql-8-0-data-dictionary-tables-and-why-they-should-stay-protected/

其中一个重要的改变就是,从8.0版本开始,MySQL废弃了.frm文件(还包括.par,.TRN,.TRG, .isl,db.opt,ddl_log.log ),MySQL使用字典信息序列化格式(serialized form)来存储数据字典,这部分数据称为serialized dictionary information (SDI),MySQL将SDI存放对应表的表空间中,并引入新的Page如:FIL_PAGE_SDI来存放SDI的数据。对于非InnoDB表,如MyISAM,SDI会存放在.sdi文件中(一种json格式的文本文件)。MySQL还提供了ibd2sdi外部命令用于从表空间文件(ibd)中读取的SDI信息。

我们可以把SDI记录看成是一张普通表,可以把FIL_PAGE_SDI页当做FIL_PAGE_INDEX (ClusteredKeyLeafPage)来解析,而SDI"表"的定义伪代码如下:

CREATE TABLE SID {
  type bytes(2) not null,  // 对应ibd2sdi的-t, --type=#参数,请参考ibd2sdi命令官方文档。
  id bytes(4) not null,     //  对应ibd2sdi的-i, --id=#参数,请参考ibd2sdi命令官方文档。
  /*DB_TRX_ID bytes(6) not null,*/
  /*DB_ROLL_PTR bytes(7) not null,*/
  uncomp_len bytes(5) not null, // zip_data解压后的长度(字节数);
  comp_len bytes(6) not null,  // zip_data的长度(字节数);
  zip_data bytes(variable-length) not null, //通过zip压缩后的SDI记录内容;
  PRIMARY KEY (type, id)
}

FIL_PAGE_SDI页的位置位于ibd文件的page(3),我们通过案例来解析该页的内容:

public class SdiPage1 {
	public static void main(String[] args) throws Exception {
		String fileName = "D:\\Data\\mysql\\8.0.18\\data\\sakila\\film.ibd";
		try (IbdFileParser parser = new IbdFileParser(fileName)) {
			SdiPage page = (SdiPage) parser.getPage(3);
			List<SdiRecord> records = page.getUserRecords();
			StringBuilder buff = new StringBuilder();
			int rows = 0;
			for(SdiRecord record: records) {
				byte[] unZipData = record.getUnZipDataRaw();
				byte[] zipData = record.getZipDataRaw();
				String json = toPretty(new String(unZipData));
				String format = "%11s : ";
				buff.append("\n*************************** ").append(++rows).append(". row ***************************\n")
					.append(String.format(format, "type")).append(record.getType()).append("\n")
					.append(String.format(format, "id")).append(record.getId()).append("\n")
					.append(String.format(format, "DB_TRX_ID")).append(ParserHelper.toHexString(record.getTrxIdRaw())).append("\n")
					.append(String.format(format, "DB_ROLL_PTR")).append(ParserHelper.toHexString(record.getRollPrtRaw())).append("\n")
					.append(String.format(format, "unzip_len")).append(record.getUncompressedLen()).append(" , actual : ").append(unZipData.length).append("\n")
					.append(String.format(format, "zip_len")).append(record.getCompressedLen()).append(" , actual : ").append(zipData.length).append("\n")
					.append("\n*************************** Content ***************************\n").append(json).append("\n");
			}
			System.out.println(buff);
		}
	}

	public static String toPretty(String jsonString) {
		JsonElement jsonElement = JsonParser.parseString(jsonString);
		Gson gson = new GsonBuilder().setPrettyPrinting().create();
		String prettyJson = gson.toJson(jsonElement);
		return prettyJson;
	}
}
/*
程序输出:
*************************** 1. row ***************************
       type : 1
         id : 521
  DB_TRX_ID : 000000738097
DB_ROLL_PTR : 020000028a15a9
  unzip_len : 16801 , actual : 16801
    zip_len : 1884 , actual : 1884

*************************** Content ***************************
{
  "mysqld_version_id": 80018,
  "dd_version": 80017,
  "sdi_version": 80016,
  "dd_object_type": "Table",
  "dd_object": {
    "name": "film",
    "mysql_version_id": 80018,
    "created": 20210319003202,
    "last_altered": 20210319003202,
    ...
}

}

*************************** 2. row ***************************
       type : 2
         id : 392
  DB_TRX_ID : 00000073803d
DB_ROLL_PTR : 82000001230535
  unzip_len : 372 , actual : 372
    zip_len : 233 , actual : 233

*************************** Content ***************************
{
  "mysqld_version_id": 80018,
  "dd_version": 80017,
  "sdi_version": 80016,
  "dd_object_type": "Tablespace",
  "dd_object": {
    "name": "sakila/film",
    "comment": "",
    "options": "encryption\u003dN;",
    "se_private_data": "flags\u003d16417;id\u003d387;server_version\u003d80018;space_version\u003d1;state\u003dnormal;",
    "engine": "InnoDB",
    "files": [
      {
        "ordinal_position": 1,
        "filename": ".\\sakila\\film.ibd",
        "se_private_data": "id\u003d387;"
      }
    ]
  }
}    
*/

与ibd2sid命令输出比对,程序解析符合预期,解析细节可以看SdiPage.class,做法就是当作ClusteredKeyLeafPage来解析,zip_data通过JDK的java.util.zip.Inflater解压。到此ibd解析系列告一段落,未来如果精力允许,而且知识储备足够,会挑战一下redo或者undo表空间结构的解析。

你可能感兴趣的:(MySQL,mysql)