为理解决这些问题(尤其是DDL无法做到atomic),从MySQL8.0开始取消了FRM文件及其他server层的元数据文件(frm, par, trn, trg, isl,db.opt),所有的元数据都用InnoDB引擎进行存储, 其余一些诸如权限表之类的系统表也改用InnoDB引擎。
本文是笔者初次理解这块内容,因此不会过多深入,由于涉及的改动太多,后面有空再逐个展开。
本文所有测试和代码干系部分都是基于MySQL8.0.0版本,由于这是8.0大版本的第一个开拓版本,不用除未来行为会发生变革。

测试
首先我们创建一个新库,并在库下创建两个表来开启我们的测试
mysql> CREATE DATABASE sbtest;
Query OK, 1 row affected (0.00 sec)
mysql> USE sbtest
Database changed
mysql> CREATE TABLE t1 (a int primary key);
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE t2 (a int primary key, b int);
Query OK, 0 rows affected (0.00 sec)$ls -lh /u01/my80/data/sbtest
total 256K
-rw-r----- 1 yinfeng.zwx users 128K Oct 5 19:44 t1.ibd
-rw-r----- 1 yinfeng.zwx users 128K Oct 5 19:44 t2.ibd$ls /u01/my80/data/sbtest_9.SDI
/u01/my80/data/sbtest_9.SDI$cat /u01/my80/data/sbtest_9.SDI
{ \"大众sdi_version\"大众: 1, \"大众dd_version\"大众: 1, \"大众dd_object_type\公众: \"大众Schema\"大众, \"大众dd_object\"大众: { \"大众name\公众: \"大众sbtest\"大众, \"大众default_collation_id\"大众: 33, \"大众created\"大众: 0, \"大众last_altered\"大众: 0
}
}
可以看到在库目录下只有ibd文件,并没有frm文件,而在数据目录下,相应的天生了一个SDI文件,来描述这个sbtest库的信息。
我们再来看看创建一个MYISAM引擎的表:
mysql> create database my;Query OK, 1 row affected (0.00 sec)
mysql> use myDatabase changed
mysql> create table t1 (a int, b varchar(320)) engine=myisam;Query OK, 0 rows affected (0.00 sec)
$ls my/
t1_435.SDI t1.MYD t1.MYI{ \公众sdi_version\"大众: 1, \公众dd_version\公众: 1, \"大众dd_object_type\公众: \"大众Table\"大众, \公众dd_object\"大众: { \"大众name\公众: \"大众t1\"大众, \"大众mysql_version_id\"大众: 80000, \公众created\"大众: 20161005201935, \"大众last_altered\"大众: 20161005201935, \公众options\公众: \"大众avg_row_length=0;key_block_size=0;keys_disabled=0;pack_record=1;stats_auto_recalc=0;stats_sample_pages=0;\"大众, \"大众columns\"大众: [
{ \"大众name\公众: \"大众a\"大众, \"大众type\"大众: 4, \"大众is_nullable\公众: true, \公众is_zerofill\公众: false, \公众is_unsigned\"大众: false, \"大众is_auto_increment\"大众: false, \"大众is_virtual\"大众: false, \"大众hidden\"大众: false, \"大众ordinal_position\公众: 1, \"大众char_length\"大众: 11, \"大众numeric_precision\"大众: 10, \"大众numeric_scale\"大众: 0, \"大众datetime_precision\"大众: 0, \"大众has_no_default\"大众: false, \公众default_value_null\"大众: true, \"大众default_value\公众: \"大众\"大众, \"大众default_option\"大众: \"大众\"大众, \"大众update_option\"大众: \公众\"大众, \"大众comment\"大众: \"大众\"大众, \"大众generation_expression\公众: \公众\公众, \"大众generation_expression_utf8\"大众: \公众\公众, \公众options\"大众: \"大众interval_count=0;\"大众, \"大众se_private_data\"大众: \"大众\公众, \"大众column_key\公众: 1, \"大众column_type_utf8\"大众: \"大众int(11)\"大众, \公众elements\"大众: [], \"大众collation_id\"大众: 33
},
{ \"大众name\"大众: \"大众b\"大众, \公众type\公众: 16, \"大众is_nullable\公众: true, \"大众is_zerofill\"大众: false, \"大众is_unsigned\"大众: false, \公众is_auto_increment\公众: false, \"大众is_virtual\"大众: false, \"大众hidden\"大众: false, \"大众ordinal_position\公众: 2, \公众char_length\"大众: 960, \公众numeric_precision\"大众: 0, \公众numeric_scale\"大众: 0, \公众datetime_precision\公众: 0, \"大众has_no_default\"大众: false, \"大众default_value_null\公众: true, \"大众default_value\"大众: \公众\"大众, \公众default_option\公众: \"大众\公众, \"大众update_option\公众: \"大众\"大众, \"大众comment\公众: \"大众\"大众, \"大众generation_expression\"大众: \"大众\"大众, \"大众generation_expression_utf8\公众: \"大众\"大众, \公众options\"大众: \"大众interval_count=0;\公众, \"大众se_private_data\公众: \"大众\公众, \公众column_key\公众: 1, \公众column_type_utf8\"大众: \公众varchar(320)\"大众, \"大众elements\公众: [], \"大众collation_id\公众: 33
}
], \"大众schema_ref\"大众: \公众my\公众, \公众hidden\"大众: false, \公众se_private_id\"大众: 18446744073709551615, \"大众engine\"大众: \"大众MyISAM\"大众, \"大众comment\公众: \"大众\"大众, \"大众se_private_data\"大众: \"大众\"大众, \"大众row_format\公众: 2, \"大众partition_type\"大众: 0, \"大众partition_expression\"大众: \"大众\公众, \公众default_partitioning\"大众: 0, \公众subpartition_type\"大众: 0, \公众subpartition_expression\"大众: \公众\"大众, \"大众default_subpartitioning\公众: 0, \公众indexes\公众: [], \"大众foreign_keys\"大众: [], \"大众partitions\"大众: [], \"大众collation_id\"大众: 33
}
}
这里我们创建了一个MyISAM表t1,相应的一个SDI文件被创建,文件中以JSON的格式记录了该表的详细信息。根据官方文件的描述,这个文件的存在是为了一个还未完备实现的功能。
新的Information Schema定义
一些新IS表利用View进行了重新设计,紧张包括这些表:
CHARACTER_SETS
COLLATIONS
COLLATION_CHARACTER_SET_APPLICABILITY
COLUMNS
KEY_COLUMN_USAGE
SCHEMATA
STATISTICS
TABLES
TABLE_CONSTRAINTS
VIEWS
#例如SCHEMATA
mysql> show create table information_schema.schemata\G
1. row View: SCHEMATA Create View: CREATE ALGORITHM=UNDEFINED DEFINER=`root`@`localhost` SQL SECURITY DEFINER VIEW `information_schema`.`SCHEMATA` AS select `cat`.`name` AS `CATALOG_NAME`,`sch`.`name` AS `SCHEMA_NAME`,`cs`.`name` AS `DEFAULT_CHARACTER_SET_NAME`,`col`.`name` AS `DEFAULT_COLLATION_NAME`,NULL AS `SQL_PATH` from (((`mysql`.`schemata` `sch` join `mysql`.`catalogs` `cat` on((`cat`.`id` = `sch`.`catalog_id`))) join `mysql`.`collations` `col` on((`sch`.`default_collation_id` = `col`.`id`))) join `mysql`.`character_sets` `cs` on((`col`.`character_set_id` = `cs`.`id`))) where can_access_database(`sch`.`name`)character_set_client: utf8collation_connection: utf8_general_ci1 row in set (0.01 sec)
也便是说,虽然DD系统表被隐蔽不可见了,但你依然可以通过视图得到大部分信息。这种办法实际上大大加快了IS表的查询速率,转换成物理表的查询后,将无需为每个IS表的查询创建临时表(临时表的操作包含了server层创建frm, 引擎层获取数据or须要锁保护的全局数据)。其余优化器也能为IS表的查询选择更好的实行操持(例如利用系统表上的索引进行查询)。
官方对此做了测试,结果显示对IS表的查询性能大幅度提升,官方博客传送门:
MySQL 8.0: Improvements to Information_schema
MySQL 8.0: Scaling and Performance of INFORMATION_SCHEMA
新选项: information_schema_stats: CACHED | LATEST
目前表的元数据信息缓存在statistics及tables表中以加速对IS表的查询性能。你可以通过参数information_schema_stats来直接读取已经缓存到内存的数据(cached),还是从存储引擎中获取最新的数据(latest). 很显然后者要慢一点。
而从is库下,可以看到对应两种表:TABLES及TABLES_DYNAMIC, 以及STATISTICS及STATISTICS_DYNAMIC。当被设置为LATEST时,就会去从_DYNAMIC表中去读取数据。
该选项也会影响到SHOW TABLES等语句的行为。
Data Dictionary Cache
数据词典的构造发生巨大的变革后,相应的对付内存数据词典Cache也做改动,
mysql> show variables like '%defin%';
+---------------------------------+-------+| Variable_name | Value |
+---------------------------------+-------+| schema_definition_cache | 256 |
| stored_program_definition_cache | 256 |
| table_definition_cache | 1400 |
| tablespace_definition_cache | 256 |
+---------------------------------+-------+
4 rows in set (0.00 sec)
tablespace_definition_cache: tablespace cache的大小,存储了tablespace的定义. 一个tablespace中可能包含多个table。
table_definition_cache:
stored_program_definition_cache: 存储过程&&function的定义cache.
schema_definition_cache: 存储schema定义的cache
hardcode的字符集cache:
character set definition cache partition: Stores character set definition objects and has a hardcoded object limit of 256.collation definition cache partition: Stores collation definition objects and has a hardcoded object limit of 256.
系统表变革
和权限干系的表转换成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下
针对不同的元数据分别定义了不同的类及其继续关系:
namespace dd {
Weak_object
Entity_object
Dictionary_object
Tablespace
Schema
Event
Routine
Function Procedure
Charset
Collation
Abstract_table
Table
Spatial_reference_system
Index_stat
View
Table_stat
Partition
Trigger
Index
Foreign_key
Parameter
Column
Partition_index
Partition_value
View_routine
View_table
Tablespace_file
Foreign_key_element
Index_element
Column_type_element
Parameter_type_element
Object_table
Dictionary_object_table
Object_type
Object_table_definition }
数据词典Cache管理类:
dd::cache { dd::cache::Dictionary_client
Object_registry
Element_map
Multi_map_base
Local_multi_map
Shared_multi_map
Cache_element
Free_list
Shared_dictionary_cache
Storage_adapter}
mysql库存储的是系统表,但通过show tables命令,我们只能看到37个表,而从磁盘来看mysql目录下ibd文件远远超过37个,这意味着有些系统表对用户是不可见的,这些表也是用于管理核心数据词典信息,不可见的缘故原由是避免用户不恰当的操作。(当然也不用除未来这一行为发生变革),关于这些表的访问,在目录sql/dd/impl/tables/
中进行了接口定义,这些隐蔽的表包括:
$grep 'std::string s_table_name' sql/dd/impl/tables/ | awk '{ print $4}'s_table_name(\公众catalogs\公众);
s_table_name(\公众character_sets\"大众);
s_table_name(\"大众collations\"大众);
s_table_name(\公众columns\"大众);
s_table_name(\"大众column_type_elements\公众);
s_table_name(\"大众events\公众);
s_table_name(\"大众foreign_key_column_usage\"大众);
s_table_name(\公众foreign_keys\公众);
s_table_name(\"大众index_column_usage\公众);
s_table_name(\公众indexes\公众);
s_table_name(\"大众index_partitions\公众);
s_table_name(\"大众index_stats\"大众);
s_table_name(\"大众parameters\"大众);
s_table_name(\"大众parameter_type_elements\"大众);
s_table_name(\"大众routines\"大众);
s_table_name(\公众schemata\公众);
s_table_name(\"大众st_spatial_reference_systems\公众);
s_table_name(\"大众table_partitions\"大众);
s_table_name(\"大众table_partition_values\公众);
s_table_name(\公众tables\"大众);
s_table_name(\"大众tablespace_files\公众);
s_table_name(\"大众tablespaces\"大众);
s_table_name(\公众table_stats\"大众);
s_table_name(\"大众triggers\"大众);
s_table_name(\"大众version\"大众);
s_table_name(\公众view_routine_usage\"大众);
s_table_name(\公众view_table_usage\"大众);
我们以对一个表的常见操作为例,看看个中一些代码是如何被调用的。
(由于New DD的代码改动很大,干系的worklog有几十个,笔者通过测试+代码debug的办法第一步先熟习代码,记录的比较缭乱)
库级操作
创建database
mysql> create database db1;Query OK, 1 row affected (2.87 sec)
mysql> create database db2;Query OK, 1 row affected (3.05 sec)
入口函数:mysql_create_db
-- 创建database目录
-- 构建binlog并写入文件
-- 调用DD API接口: dd::create_schema
构建工具dd::Schema 存储到数据词典中mysql.schemata表中,干系堆栈:
``` dd::create_schema
|--> dd::cache::Dictionary_client::store<dd::Schema>
|--> dd::cache::Storage_adapter::store<dd::Schema>
|--> dd::Weak_object_impl::store
|--> dd::Raw_new_record::insert
Note: schemata表对用户是不可见的
mysql> desc schemata; ERROR 3554 (HY000): Access to system table 'mysql.schemata' is rejected.
```
创建并存储当前库的信息到SDI文件中,sdi文件命名以库名为前缀,堆栈如下
``` dd::create_schema
|--> dd::store_sdi
|--> dd::sdi_file::store
|--> write_sdi_file
```
成功则commit,失落败则rollback
修正database
mysql> alter database db1 default charset gbk;
Query OK, 1 row affected (2 min 17.54 sec)
入口函数: mysql_alter_db
-- 调用DD API接口: dd::alter_schema
更新数据词典信息,干系堆栈:```dd::alter_schema|--> dd::cache::Dictionary_client::update<dd::Schema>
|--> dd::cache::Dictionary_client::store<dd::Schema>
|--> dd::cache::Storage_adapter::store<dd::Schema>
|--> dd::Weak_object_impl::store
|--> dd::Raw_record::update```
更新sdi文件, 干系堆栈
```dd::alter_schema|--> dd::Sdi_updater::operator()
|--> dd::update_sdi
|--> dd::sdi_file::store
|--> write_sdi_file
```
但奇怪的是,更新后很快就删除了 ?? (8.0.0版本,why ??)
看起来sdi文件的序列号没有递增,导致文件被快速删除了,实际上的目的是创建一个新的文件,写入新的数据,然后将老的SDI删掉ref: http://bugs.mysql.com/bug.php?id=83281
-- 写Binlog
show databases
mysql> show databases;
+--------------------+| Database |
+--------------------+| db1 |
| db2 |
| information_schema |
| mysql |
| performance_schema || sys |
+--------------------+6 rows in set (1.40 sec)
实行该命令时,实际上会对其进行一个SQL转换,将其转换成一个标准的查询语句,堆栈如下:
dispatch_command
|-->mysql_parse
|-->parse_sql
|-->MYSQLparse
|--> dd::info_schema::build_show_databases_query
转换后的SQL类似:
SELECT SCHEMA_NAME as `Database`, FROM information_schema.schemata;
由于直接从系统表中读取, 这意味着在数据目录下创建一个文件夹将不会当作新的数据库目录。
删除database
mysql> drop database db2;
Query OK, 0 rows affected (1 min 1.86 sec)
-- 删除干系文件
-- 删除系统表mysql/schemata中记录
mysql_rm_db
|--> dd::drop_schema
|--> dd::cache::Dictionary_client::drop<dd::Schema>
|-->dd::cache::Storage_adapter::drop<dd::Schema>
|--> dd::Weak_object_impl::drop
|--> dd::Raw_record::drop
|--> handler::ha_delete_row
表级操作
创建表
mysql> create table t1 (a int primary key, b int, c int, key(b));
Query OK, 0 rows affected (7 min 12.29 sec)
入口函数:
mysql_create_table_no_lock|--> create_table_impl
|--> rea_create_table
-- 先在dd中插入新的记录(dd::create_table
--> dd::create_dd_user_table
)
// 根据建表语句初始化dd::Table
工具,包括表的列定义,各个属性和选项,索引定义
// 存到系统表中
```
dd::create_dd_user_table
|--> dd::cache::Dictionary_client::storedd::Table
|-->dd::cache::Storage_adapter::storedd::Table
|-->dd::Weak_object_impl::store
// 先插入到mysql/tables系统表中
// 再插入到其他系统表中,如\公众mysql/columns\公众,
|-->dd::Table_impl::store_children
|--> dd::Abstract_table_impl::store_children // mysql/columns
|--> dd::Collection<dd::Column>::store_items
|--> Weak_object_impl::store
|-->dd::Collection<dd::Index>::store_items // mysql/indexes
|--> dd::Weak_object_impl::store
|-->dd::Index_impl::store_children
|--> dd::Collection<dd::Index_element>::store_items // mysql/index_column_usage```
-- 然后再创建引擎文件
Open table
-- 将实例重启后,然后再打开表,表定义第一次载入内存,须要先去访问系统表拿到表定义:
open_and_process_table
|-->open_table
|-->get_table_share_with_discover
|-->get_table_share
|-->open_table_def // 先看schema是否存在,并从系统表`mysql/schemata`载入内存cache中
|-->dd::schema_exists
|--> dd::cache::Dictionary_client::acquire<dd::Schema>
|-->dd::cache::Dictionary_client::acquire<dd::Item_name_key, dd::Schema>
|-->dd::cache::Shared_dictionary_cache::get<dd::Item_name_key, dd::Schema>
|-->dd::cache::Shared_dictionary_cache::get_uncached<dd::Item_name_key, dd::Schema>
|-->dd::cache::Storage_adapter::get<dd::Item_name_key, dd::Schema>
|-->dd::Raw_table::find_record
// 再获取表的定义并从系统表mysql/tables载入
|-->dd::abstract_table_type
|-->dd::cache::Dictionary_client::acquire<dd::Abstract_table>
|-->dd::cache::Dictionary_client::acquire<dd::Item_name_key, dd::Abstract_table>
|-->dd::cache::Shared_dictionary_cache::get<dd::Item_name_key, dd::Abstract_table>
|-->dd::cache::Shared_dictionary_cache::get_uncached<dd::Item_name_key, dd::Abstract_table>
|-->dd::cache::Storage_adapter::get<dd::Item_name_key, dd::Abstract_table>
|--> dd::Raw_table::find_record
// 获取表上的属性信息
|-->Dictionary_object_table_impl::restore_object_from_record
|-->dd::Table_impl::restore_children
|-->dd::Abstract_table_impl::restore_children
// 从mysql/columns系统表得到列信息
|-->dd::Collection<dd::Column>::restore_items<dd::Abstract_table_impl> // 从mysql/indexs系统表得到索引信息
|-->dd::Collection<dd::Index>::restore_items<dd::Table_impl> //从mysql/index_column_usage获取索引信息
|-->dd::Collection<dd::Index_element>::restore_items<dd::Index_impl> // 从mysql/foreign_keys得到外键信息
|-->dd::Collection<dd::Foreign_key>::restore_items<dd::Table_impl> // 从mysql/table_partitions得到分区信息
|-->dd::Collection<dd::Partition>::restore_items<dd::Table_impl> //从\"大众mysql/triggers得到触发器信息
|-->dd::Collection<dd::Trigger>::restore_items<dd::Table_impl>
干系WorkLog
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