天天看点

MySQL · 8.0新特性 · New data dictionary尝鲜篇

众所周知,由于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> --&gt; <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>