当前位置:   article > 正文

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

error 3554 (hy000): access to system table

众所周知,由于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大版本的第一个开发版本,不排除未来行为会发生变化。

测试

首先我们创建一个新库,并在库下创建两个表来开启我们的测试

  1. mysql> CREATE DATABASE sbtest;
  2. Query OK, 1 row affected (0.00 sec)
  3. mysql> USE sbtest
  4. Database changed
  5. mysql> CREATE TABLE t1 (a int primary key);
  6. Query OK, 0 rows affected (0.00 sec)
  7. mysql> CREATE TABLE t2 (a int primary key, b int);
  8. Query OK, 0 rows affected (0.00 sec)
  9. $ls -lh /u01/my80/data/sbtest
  10. total 256K
  11. -rw-r----- 1 yinfeng.zwx users 128K Oct 5 19:44 t1.ibd
  12. -rw-r----- 1 yinfeng.zwx users 128K Oct 5 19:44 t2.ibd
  13. $ls /u01/my80/data/sbtest_9.SDI
  14. /u01/my80/data/sbtest_9.SDI
  15. $cat /u01/my80/data/sbtest_9.SDI
  16. {
  17. "sdi_version": 1,
  18. "dd_version": 1,
  19. "dd_object_type": "Schema",
  20. "dd_object": {
  21. "name": "sbtest",
  22. "default_collation_id": 33,
  23. "created": 0,
  24. "last_altered": 0
  25. }
  26. }

可以看到在库目录下只有ibd文件,并没有frm文件,而在数据目录下,相应的生成了一个SDI文件,来描述这个sbtest库的信息。

我们再来看看创建一个MYISAM引擎的表:

  1. mysql> create database my;
  2. Query OK, 1 row affected (0.00 sec)
  3. mysql> use my
  4. Database changed
  5. mysql> create table t1 (a int, b varchar(320)) engine=myisam;
  6. Query OK, 0 rows affected (0.00 sec)
  7. $ls my/
  8. t1_435.SDI t1.MYD t1.MYI
  9. {
  10. "sdi_version": 1,
  11. "dd_version": 1,
  12. "dd_object_type": "Table",
  13. "dd_object": {
  14. "name": "t1",
  15. "mysql_version_id": 80000,
  16. "created": 20161005201935,
  17. "last_altered": 20161005201935,
  18. "options": "avg_row_length=0;key_block_size=0;keys_disabled=0;pack_record=1;stats_auto_recalc=0;stats_sample_pages=0;",
  19. "columns": [
  20. {
  21. "name": "a",
  22. "type": 4,
  23. "is_nullable": true,
  24. "is_zerofill": false,
  25. "is_unsigned": false,
  26. "is_auto_increment": false,
  27. "is_virtual": false,
  28. "hidden": false,
  29. "ordinal_position": 1,
  30. "char_length": 11,
  31. "numeric_precision": 10,
  32. "numeric_scale": 0,
  33. "datetime_precision": 0,
  34. "has_no_default": false,
  35. "default_value_null": true,
  36. "default_value": "",
  37. "default_option": "",
  38. "update_option": "",
  39. "comment": "",
  40. "generation_expression": "",
  41. "generation_expression_utf8": "",
  42. "options": "interval_count=0;",
  43. "se_private_data": "",
  44. "column_key": 1,
  45. "column_type_utf8": "int(11)",
  46. "elements": [],
  47. "collation_id": 33
  48. },
  49. {
  50. "name": "b",
  51. "type": 16,
  52. "is_nullable": true,
  53. "is_zerofill": false,
  54. "is_unsigned": false,
  55. "is_auto_increment": false,
  56. "is_virtual": false,
  57. "hidden": false,
  58. "ordinal_position": 2,
  59. "char_length": 960,
  60. "numeric_precision": 0,
  61. "numeric_scale": 0,
  62. "datetime_precision": 0,
  63. "has_no_default": false,
  64. "default_value_null": true,
  65. "default_value": "",
  66. "default_option": "",
  67. "update_option": "",
  68. "comment": "",
  69. "generation_expression": "",
  70. "generation_expression_utf8": "",
  71. "options": "interval_count=0;",
  72. "se_private_data": "",
  73. "column_key": 1,
  74. "column_type_utf8": "varchar(320)",
  75. "elements": [],
  76. "collation_id": 33
  77. }
  78. ],
  79. "schema_ref": "my",
  80. "hidden": false,
  81. "se_private_id": 18446744073709551615,
  82. "engine": "MyISAM",
  83. "comment": "",
  84. "se_private_data": "",
  85. "row_format": 2,
  86. "partition_type": 0,
  87. "partition_expression": "",
  88. "default_partitioning": 0,
  89. "subpartition_type": 0,
  90. "subpartition_expression": "",
  91. "default_subpartitioning": 0,
  92. "indexes": [],
  93. "foreign_keys": [],
  94. "partitions": [],
  95. "collation_id": 33
  96. }
  97. }

这里我们创建了一个MyISAM表t1,相应的一个SDI文件被创建,文件中以JSON的格式记录了该表的详细信息。根据官方文件的描述,这个文件的存在是为了一个还未完全实现的功能。

新的Information Schema定义

一些新IS表使用View进行了重新设计,主要包括这些表:

  1. CHARACTER_SETS
  2. COLLATIONS
  3. COLLATION_CHARACTER_SET_APPLICABILITY
  4. COLUMNS
  5. KEY_COLUMN_USAGE
  6. SCHEMATA
  7. STATISTICS
  8. TABLES
  9. TABLE_CONSTRAINTS
  10. VIEWS
  11. #例如SCHEMATA
  12. mysql> show create table information_schema.schemata\G
  13. *************************** 1. row ***************************
  14. View: SCHEMATA
  15. 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`)
  16. character_set_client: utf8
  17. collation_connection: utf8_general_ci
  18. 1 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

  1. 目前表的元数据信息缓存在statistics及tables表中以加速对IS表的查询性能。你可以通过参数information_schema_stats来直接读取已经缓存到内存的数据(cached),还是从存储引擎中获取最新的数据(latest). 很显然后者要慢一点。
  2. 而从is库下,可以看到对应两种表:TABLES及TABLES_DYNAMIC, 以及STATISTICS及STATISTICS_DYNAMIC。当被设置为LATEST时,就会去从**_DYNAMIC表中去读取数据。
  3. 该选项也会影响到SHOW TABLES等语句的行为。

Data Dictionary Cache

数据词典的结构发生巨大的变化后,相应的对于内存数据词典Cache也做改动,

  1. mysql> show variables like '%defin%';
  2. +---------------------------------+-------+
  3. | Variable_name | Value |
  4. +---------------------------------+-------+
  5. | schema_definition_cache | 256 |
  6. | stored_program_definition_cache | 256 |
  7. | table_definition_cache | 1400 |
  8. | tablespace_definition_cache | 256 |
  9. +---------------------------------+-------+
  10. 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:

  1. character set definition cache partition: Stores character set definition objects and has a hardcoded object limit of 256.
  2. 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下

针对不同的元数据分别定义了不同的类及其继承关系:

  1. namespace dd {
  2. Weak_object
  3. Entity_object
  4. Dictionary_object
  5. Tablespace
  6. Schema
  7. Event
  8. Routine
  9. Function
  10. Procedure
  11. Charset
  12. Collation
  13. Abstract_table
  14. Table
  15. Spatial_reference_system
  16. Index_stat
  17. View
  18. Table_stat
  19. Partition
  20. Trigger
  21. Index
  22. Foreign_key
  23. Parameter
  24. Column
  25. Partition_index
  26. Partition_value
  27. View_routine
  28. View_table
  29. Tablespace_file
  30. Foreign_key_element
  31. Index_element
  32. Column_type_element
  33. Parameter_type_element
  34. Object_table
  35. Dictionary_object_table
  36. Object_type
  37. Object_table_definition
  38. }

数据词典Cache管理类:

  1. dd::cache {
  2. dd::cache::Dictionary_client
  3. Object_registry
  4. Element_map
  5. Multi_map_base
  6. Local_multi_map
  7. Shared_multi_map
  8. Cache_element
  9. Free_list
  10. Shared_dictionary_cache
  11. Storage_adapter
  12. }

mysql库存储的是系统表,但通过show tables命令,我们只能看到37个表,而从磁盘来看mysql目录下ibd文件远远超过37个,这意味着有些系统表对用户是不可见的,这些表也是用于管理核心数据词典信息,不可见的原因是避免用户不恰当的操作。(当然也不排除未来这一行为发生变化),关于这些表的访问,在目录sql/dd/impl/tables/中进行了接口定义,这些隐藏的表包括:

  1. $grep 'std::string s_table_name' sql/dd/impl/tables/* | awk '{ print $4}'
  2. s_table_name("catalogs");
  3. s_table_name("character_sets");
  4. s_table_name("collations");
  5. s_table_name("columns");
  6. s_table_name("column_type_elements");
  7. s_table_name("events");
  8. s_table_name("foreign_key_column_usage");
  9. s_table_name("foreign_keys");
  10. s_table_name("index_column_usage");
  11. s_table_name("indexes");
  12. s_table_name("index_partitions");
  13. s_table_name("index_stats");
  14. s_table_name("parameters");
  15. s_table_name("parameter_type_elements");
  16. s_table_name("routines");
  17. s_table_name("schemata");
  18. s_table_name("st_spatial_reference_systems");
  19. s_table_name("table_partitions");
  20. s_table_name("table_partition_values");
  21. s_table_name("tables");
  22. s_table_name("tablespace_files");
  23. s_table_name("tablespaces");
  24. s_table_name("table_stats");
  25. s_table_name("triggers");
  26. s_table_name("version");
  27. s_table_name("view_routine_usage");
  28. s_table_name("view_table_usage");

我们以对一个表的常见操作为例,看看其中一些代码是如何被调用的。
(由于New DD的代码改动很大,相关的worklog有几十个,笔者通过测试+代码debug的方式第一步先熟悉代码,记录的比较凌乱)

库级操作
  1. 创建database
  1. mysql> create database db1;
  2. Query OK, 1 row affected (2.87 sec)
  3. mysql> create database db2;
  4. Query OK, 1 row affected (3.05 sec)

入口函数:mysql_create_db

-- 创建database目录
-- 构建binlog并写入文件
-- 调用DD API接口: dd::create_schema

  1. * 构建对象dd::Schema
  2. * 存储到数据词典中mysql.schemata表中,相关堆栈:
  1. dd::create_schema
  2. |--> dd::cache::Dictionary_client::store<dd::Schema>
  3. |--> dd::cache::Storage_adapter::store<dd::Schema>
  4. |--> dd::Weak_object_impl::store
  5. |--> dd::Raw_new_record::insert
  6. Note: schemata表对用户是不可见的
  7. mysql> desc schemata;
  8. ERROR 3554 (HY000): Access to system table 'mysql.schemata' is rejected.
  1. * 创建并存储当前库的信息到SDI文件中,sdi文件命名以库名为前缀,堆栈如下
  1. dd::create_schema
  2. |--> dd::store_sdi
  3. |--> dd::sdi_file::store
  4. |--> write_sdi_file
  1. * 成功则commit,失败则rollback
  1. 修改database
  1. mysql> alter database db1 default charset gbk;
  2. Query OK, 1 row affected (2 min 17.54 sec)

入口函数: mysql_alter_db

-- 调用DD API接口: dd::alter_schema

* 更新数据词典信息,相关堆栈:
  1. dd::alter_schema
  2. |--> dd::cache::Dictionary_client::update<dd::Schema>
  3. |--> dd::cache::Dictionary_client::store<dd::Schema>
  4. |--> dd::cache::Storage_adapter::store<dd::Schema>
  5. |--> dd::Weak_object_impl::store
  6. |--> dd::Raw_record::update
  1. *更新sdi文件, 相关堆栈
  1. dd::alter_schema
  2. |--> dd::Sdi_updater::operator()
  3. |--> dd::update_sdi
  4. |--> dd::sdi_file::store
  5. |--> write_sdi_file
  1. *但奇怪的是,更新后很快就删除了 ?? (8.0.0版本,why ??)
  2. 看起来sdi文件的序列号没有递增,导致文件被快速删除了,实际上的目的是创建一个新的文件,写入新的数据,然后将老的SDI删掉
  3. ref: http://bugs.mysql.com/bug.php?id=83281

-- 写Binlog

  1. show databases
  1. mysql> show databases;
  2. +--------------------+
  3. | Database |
  4. +--------------------+
  5. | db1 |
  6. | db2 |
  7. | information_schema |
  8. | mysql |
  9. | performance_schema |
  10. | sys |
  11. +--------------------+
  12. 6 rows in set (1.40 sec)

执行该命令时,实际上会对其进行一个SQL转换,将其转换成一个标准的查询语句,堆栈如下:

  1. dispatch_command
  2. |-->mysql_parse
  3. |-->parse_sql
  4. |-->MYSQLparse
  5. |--> dd::info_schema::build_show_databases_query

转换后的SQL类似:

  1. SELECT SCHEMA_NAME as `Database`,
  2. FROM information_schema.schemata;

由于直接从系统表中读取, 这意味着在数据目录下创建一个文件夹将不会当作新的数据库目录。

  1. 删除database
  1. mysql> drop database db2;
  2. Query OK, 0 rows affected (1 min 1.86 sec)

-- 删除相关文件

-- 删除系统表mysql/schemata中记录

  1. mysql_rm_db
  2. |--> dd::drop_schema
  3. |--> dd::cache::Dictionary_client::drop<dd::Schema>
  4. |-->dd::cache::Storage_adapter::drop<dd::Schema>
  5. |--> dd::Weak_object_impl::drop
  6. |--> dd::Raw_record::drop
  7. |--> handler::ha_delete_row
表级操作
  1. 创建表
  1. mysql> create table t1 (a int primary key, b int, c int, key(b));
  2. Query OK, 0 rows affected (7 min 12.29 sec)

入口函数:

  1. mysql_create_table_no_lock
  2. |--> create_table_impl
  3. |--> rea_create_table

-- 先在dd中插入新的记录(dd::create_table --> dd::create_dd_user_table)

  1. // 根据建表语句初始化`dd::Table` 对象,包括表的列定义,各个属性和选项,索引定义
  2. // 存到系统表中
  1. dd::create_dd_user_table
  2. |--> dd::cache::Dictionary_client::store<dd::Table>
  3. |-->dd::cache::Storage_adapter::store<dd::Table>
  4. |-->dd::Weak_object_impl::store
  5. // 先插入到mysql/tables系统表中
  6. // 再插入到其他系统表中,如"mysql/columns",
  7. |-->dd::Table_impl::store_children
  8. |--> dd::Abstract_table_impl::store_children // mysql/columns
  9. |--> dd::Collection<dd::Column*>::store_items
  10. |--> Weak_object_impl::store
  11. |-->dd::Collection<dd::Index*>::store_items // mysql/indexes
  12. |--> dd::Weak_object_impl::store
  13. |-->dd::Index_impl::store_children
  14. |--> dd::Collection<dd::Index_element*>::store_items // mysql/index_column_usage

-- 然后再创建引擎文件

  1. Open table

-- 将实例重启后,然后再打开表,表定义第一次载入内存,需要先去访问系统表拿到表定义:

  1. open_and_process_table
  2. |-->open_table
  3. |-->get_table_share_with_discover
  4. |-->get_table_share
  5. |-->open_table_def
  6. // 先看schema是否存在,并从系统表`mysql/schemata`载入内存cache中
  7. |-->dd::schema_exists
  8. |--> dd::cache::Dictionary_client::acquire<dd::Schema>
  9. |-->dd::cache::Dictionary_client::acquire<dd::Item_name_key, dd::Schema>
  10. |-->dd::cache::Shared_dictionary_cache::get<dd::Item_name_key, dd::Schema>
  11. |-->dd::cache::Shared_dictionary_cache::get_uncached<dd::Item_name_key, dd::Schema>
  12. |-->dd::cache::Storage_adapter::get<dd::Item_name_key, dd::Schema>
  13. |-->dd::Raw_table::find_record
  14. // 再获取表的定义并从系统表mysql/tables载入
  15. |-->dd::abstract_table_type
  16. |-->dd::cache::Dictionary_client::acquire<dd::Abstract_table>
  17. |-->dd::cache::Dictionary_client::acquire<dd::Item_name_key, dd::Abstract_table>
  18. |-->dd::cache::Shared_dictionary_cache::get<dd::Item_name_key, dd::Abstract_table>
  19. |-->dd::cache::Shared_dictionary_cache::get_uncached<dd::Item_name_key, dd::Abstract_table>
  20. |-->dd::cache::Storage_adapter::get<dd::Item_name_key, dd::Abstract_table>
  21. |--> dd::Raw_table::find_record
  22. // 获取表上的属性信息
  23. |-->Dictionary_object_table_impl::restore_object_from_record
  24. |-->dd::Table_impl::restore_children
  25. |-->dd::Abstract_table_impl::restore_children
  26. // 从mysql/columns系统表获得列信息
  27. |-->dd::Collection<dd::Column*>::restore_items<dd::Abstract_table_impl>
  28. // 从mysql/indexs系统表获得索引信息
  29. |-->dd::Collection<dd::Index*>::restore_items<dd::Table_impl>
  30. //从mysql/index_column_usage获取索引信息
  31. |-->dd::Collection<dd::Index_element*>::restore_items<dd::Index_impl>
  32. // 从mysql/foreign_keys获得外键信息
  33. |-->dd::Collection<dd::Foreign_key*>::restore_items<dd::Table_impl>
  34. // 从mysql/table_partitions获得分区信息
  35. |-->dd::Collection<dd::Partition*>::restore_items<dd::Table_impl>
  36. //"mysql/triggers获得触发器信息
  37. |-->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

官方博客:
https://mysqlserverteam.com/mysql-server-bootstrapping-and-dictionary-initialization/
https://mysqlserverteam.com/bootstrapping-the-transactional-data-dictionary/

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/628210
推荐阅读
相关标签
  

闽ICP备14008679号