众所周知,由于mysql采用统一server层+不同的底层引擎插件的架构模式,在server层为每个表创建了frm文件,以保存与表定义相关的元数据信息。然而某些引擎(例如innodb)本身也会存储元数据,这样不仅产生了元数据冗余,而且由于server层和引擎层分别各自管理,在执行ddl之类的操作时,很难做到crash-safe,更别说让ddl具备事务性了。
为了解决这些问题(尤其是ddl无法做到atomic),从mysql8.0开始取消了frm文件及其他server层的元数据文件(frm, par, trn, trg, isl,db.opt),所有的元数据都用innodb引擎进行存储, 另外一些诸如权限表之类的系统表也改用innodb引擎。
本文是笔者初次了解这块内容,因此不会过多深入,由于涉及的改动太多,后面有空再逐个展开。
本文所有测试和代码相关部分都是基于mysql8.0.0版本,由于这是8.0大版本的第一个开发版本,不排除未来行为会发生变化。
首先我们创建一个新库,并在库下创建两个表来开启我们的测试
可以看到在库目录下只有ibd文件,并没有frm文件,而在数据目录下,相应的生成了一个sdi文件,来描述这个sbtest库的信息。
我们再来看看创建一个myisam引擎的表:
这里我们创建了一个myisam表t1,相应的一个sdi文件被创建,文件中以json的格式记录了该表的详细信息。根据官方文件的描述,这个文件的存在是为了一个还未完全实现的功能。
一些新is表使用view进行了重新设计,主要包括这些表:
也就是说,虽然dd系统表被隐藏不可见了,但你依然可以通过视图获得大部分信息。这种方式实际上大大加快了is表的查询速度,转换成物理表的查询后,将无需为每个is表的查询创建临时表(临时表的操作包含了server层创建frm, 引擎层获取数据or需要锁保护的全局数据)。另外优化器也能为is表的查询选择更好的执行计划(例如使用系统表上的索引进行查询)。
官方对此做了测试,结果显示对is表的查询性能大幅度提升,官方博客传送门:
<a href="http://mysqlserverteam.com/mysql-8-0-improvements-to-information_schema/">mysql 8.0: improvements to information_schema</a>
<a href="http://mysqlserverteam.com/mysql-8-0-scaling-and-performance-of-information_schema/">mysql 8.0: scaling and performance of information_schema</a>
数据词典的结构发生巨大的变化后,相应的对于内存数据词典cache也做改动,
hardcode的字符集cache:
和权限相关的表转换成innodb引擎
// 包括:user, db, tables_priv, columns_priv, procs_priv, proxies_priv
func表转换成innodb事务表
// 基于此变化,对function的操作(例如create function或者drop function, 或者用户定义的udf)可能会导致一次隐式提交
mysql库下的routine表及event表不再使用,这些信息被存储到新的dd table中,并且在mysql库下是不可见的。
外键系统表
// 使用两个不可见的系统表foreign_keys和foreign_key_column_usage来存储外键信息
// 由于这两个系统表不可见,你需要通过is库下的referential_constraints和key_column_usage表来获得外键信息
// 引入的不兼容:foreign key的名字不可以超过64个字符(之前版本是允许的)
我们回到源代码目录下,大量的新代码文件被引入,以从server层管理new dd,主要定义了一系列统一的api,代码存于sql/dd目录下,函数和类定义在namespace dd下
针对不同的元数据分别定义了不同的类及其继承关系:
数据词典cache管理类:
mysql库存储的是系统表,但通过show tables命令,我们只能看到37个表,而从磁盘来看mysql目录下ibd文件远远超过37个,这意味着有些系统表对用户是不可见的,这些表也是用于管理核心数据词典信息,不可见的原因是避免用户不恰当的操作。(当然也不排除未来这一行为发生变化),关于这些表的访问,在目录<code>sql/dd/impl/tables/</code>中进行了接口定义,这些隐藏的表包括:
我们以对一个表的常见操作为例,看看其中一些代码是如何被调用的。
(由于new dd的代码改动很大,相关的worklog有几十个,笔者通过测试+代码debug的方式第一步先熟悉代码,记录的比较凌乱)
创建database
入口函数:<code>mysql_create_db</code>
-- 创建database目录
-- 构建binlog并写入文件
-- 调用dd api接口: dd::create_schema
修改database
入口函数: <code>mysql_alter_db</code>
-- 调用dd api接口: <code>dd::alter_schema</code>
-- 写binlog
show databases
执行该命令时,实际上会对其进行一个sql转换,将其转换成一个标准的查询语句,堆栈如下:
转换后的sql类似:
由于直接从系统表中读取, 这意味着在数据目录下创建一个文件夹将不会当作新的数据库目录。
删除database
-- 删除相关文件
-- 删除系统表mysql/schemata中记录
创建表
入口函数:
-- 先在dd中插入新的记录(<code>dd::create_table</code> --> <code>dd::create_dd_user_table</code>)
-- 然后再创建引擎文件
open table
-- 将实例重启后,然后再打开表,表定义第一次载入内存,需要先去访问系统表拿到表定义:
wl#6379: schema definitions for new dd
wl#6380: formulate framework for api for dd
wl#6381: handler api changes for new dictionary
wl#6382: define and implement api for table objects
wl#6383: define and implement api for triggers
wl#6384: define and implement api for stored routines
wl#6385: define and implement api for schema
wl#6387: define and implement api for tablespaces
wl#6388: define and implement api for events
wl#6389: define and implement api for views
wl#6390: use new dd api for handling non-partitioned tables
wl#6391: protect data dictionary tables
wl#6392: upgrade to transactional data dictionary
wl#6394: bootstrap code for new dd
wl#6416: innodb: remove the use of *.isl files
wl#6599: new data dictionary and i_s integration
wl#6929: move foreign key constraints to the global data dictionary
wl#7053: innodb: provide storage for tablespace dictionary
wl#7066: external tool to extract innodb tablespace dictionary information
wl#7069: provide data dictionary information in serialized form
wl#7167: change ddl to update rows for view columns in dd.columns and other dependent values.
wl#7284: implement common code for different dd apis
wl#7464: innodb: provide a way to do non-locking reads
wl#7488: innodb startup refactoring
wl#7630: define and implement api for table partition info
wl#7771: make sure errors are properly handled in dd api
wl#7784: store temporary table metadata in memory
wl#7836: use new dd api for handling partitioned tables
wl#7896: use dd api to work with triggers
wl#7897: use dd api to work with stored routines
wl#7898: use dd api to work with events
wl#7907: runtime: use non-locking reads for dd tables under i_s view.
wl#8150: dictionary object cache
wl#8433: separate dd commands from regular sql queries in the parser grammar
wl#8980: move udf table from myisam to transactional storage
wl#9045: make user management ddls atomic
官方博客:
<a href="https://mysqlserverteam.com/mysql-server-bootstrapping-and-dictionary-initialization/">https://mysqlserverteam.com/mysql-server-bootstrapping-and-dictionary-initialization/</a>
<a href="https://mysqlserverteam.com/bootstrapping-the-transactional-data-dictionary/">https://mysqlserverteam.com/bootstrapping-the-transactional-data-dictionary/</a>