MySQL·8.0新特性·Newdatadictionary嚐鮮篇
眾所周知,由於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大版本的第一個開發版本,不排除未來行為會發生變化。
測試
首先我們建立一個新庫,並在庫下建立兩個表來開啟我們的測試
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 my
Database 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.schemataG
*************************** 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: utf8
collation_connection: utf8_general_ci
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
目前表的後設資料資訊快取在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。
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::store<dd::Table>
|-->dd::cache::Storage_adapter::store<dd::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
官方部落格:
https://mysqlserverteam.com/mysql-server-bootstrapping-and-dictionary-initialization/
https://mysqlserverteam.com/bootstrapping-the-transactional-data-dictionary/
相關文章
- TiDB 4.0 新特性嚐鮮指南TiDB
- Webpack5.0 新特性嚐鮮實戰 ??Web
- MySQL 8.0 新特性MySql
- 新特性解讀 | MySQL 8.0 新密碼策略(終篇)MySql密碼
- MySQL 8.0新特性概覽MySql
- MySQL8.0-新特性彙總MySql
- MySQL8.0-新特性-DescendingIndexMySqlIndex
- MySQL 8.0 新特性梳理彙總MySql
- MySQL·8.0新特性·InvisibleIndexMySqlIndex
- MySQL 8.0新特性更新介紹MySql
- MySQL8.0 新特性 top10MySql
- Mysql8.0部分新特性MySql
- mysql8.0新特性--隱藏索引MySql索引
- 求不更學不動之Redis5.0新特性Stream嚐鮮Redis
- MySQL 8.0新特性-倒敘索引 desc indexMySql索引Index
- MySQL8.0新特性-CTE語法支援MySql
- MySQL8.0 新特性:Partial Update of LOB ColumnMySql
- 【Flutter桌面篇】Flutter&Windows應用嚐鮮FlutterWindows
- MySQL 8.0 新增特性MySql
- React Suspense 嚐鮮React
- Scheme嚐鮮Scheme
- MySQL8.0新特性-臨時表的改善MySql
- MySQL 8.0 18個管理相關的新特性MySql
- 我還在生產玩 JDK7,JDK 15 卻要來了!|新特性嚐鮮JDK
- 新特性解讀 | MySQL 8.0 多因素身份認證MySql
- 新特性解讀 | MySQL 8.0 對 UNION 的改進MySql
- MySQL 8.0表空間新特性簡單實驗MySql
- React Loops 嚐鮮ReactOOP
- 鴻蒙系統嚐鮮鴻蒙
- 8.0新特性-不可見索引索引
- MySQL8.0新特性隨筆:NOWAIT以及SKIPLOCKEDMySqlAI
- 嚐鮮Oracle Database 12c的十二大新特性VKOracleDatabase
- MySQL 8.0 新增特性介紹MySql
- Windows 10 週年版嚐鮮Windows
- Go 1.17 泛型嚐鮮Go泛型
- Vue.js 2.6嚐鮮Vue.js
- MySQL 5.6, 5.7, 8.0版本的新特性彙總大全MySql
- Node.js 嚐鮮筆記Node.js筆記