首页 » SEO优化 » php辞书源码技巧_解读MySQL80数据字典重构源码

php辞书源码技巧_解读MySQL80数据字典重构源码

访客 2024-11-10 0

扫一扫用手机浏览

文章目录 [+]

本文分享自华为云社区《【华为云MySQL技能专栏】MySQL8 数据字典重构源码解读-云社区-华为云》,作者:GaussDB 数据库

1.背景先容

在MySQL5.7 版本的利用实践过程中,我们很随意马虎碰着DDL崩溃后导致数据不一致的问题,详细场景描述如下:

php辞书源码技巧_解读MySQL80数据字典重构源码

主备高可用架构支配下,备机回放实行DROP TABLE的中途,因触发其它社区bug导致备机mysqld进程crash。
重新拉起备机后,因存储表构造的FRM文件与表空间IBD没有被同时清理,导致再次实行DROP TABLE失落败,需手动清理备机物理文件,这给自动化运维带来了很大阻碍。

php辞书源码技巧_解读MySQL80数据字典重构源码
(图片来自网络侵删)

这个问题的本色是MySQL5.7 版本的DDL非原子性、数据字典的架构是有缺陷的。
MySQL 社区从5.7到8.0版本的演进过程中,个中一大改动是对数据字典(Data dictionary, 下文缩写为DD)的重构及与之干系原子性Data Definition Language (Atomic DDL)的支持。
重构的动机来源于5.7版本数据字典的以下问题[1]:

(1)Server层和存储引擎插件层的数据字典未统一。
Server和存储引擎分别掩护了各自的DD信息,导致部分DD信息冗余,进而带来DD信息不同步的隐患。

(2)不同类型的DD文件缺少统一的访问API,不利于后续的掩护和拓展。

(3)非原子DDL:数据字典被存放在非事务的表里。
如果mysqld在DDL中途crash,会导致数据残留和复制问题。

(4) Information_schema的性能受到批评。
5.7版本中,Information_schema表的定义是临时表,这些临时表的数据来源于FRM文件、存储引擎的统计信息等。
最紧张的缺陷是与表构造文件FRM的交互会导致大量I/O开销,性能较差[2]。

下文将剖析MySQL8.0 版本数据字典重构干系的代码,并阐明重构后如何办理在5.7 版本中存在的干系问题。

2. 数据字典的变革

DD的重构是如何影响DDL语句流程和锁系统的呢?可以先从最常见的CREATE TABLE,即创非临时表DDL的场景入手不雅观察。

2.1 5.7 vs 8.0 创表流程比拟

比拟5.7和8.0创表流程中的紧张接口:

5.7流程:

(server层)mysql_create_table|-open_tables->lock_table_names // 对schema上IX锁|-mysql_create_table_no_lock->create_table_impl|-access检讨是否有重名FRM|-get_cached_table_share // server层校验表名是否存在|-ha_table_exists_in_engine // 调存储引擎的HA接口,但大多数存储引擎没实现|-rea_create_table|-mysql_create_frm//持久化:server层表构造文件FRM|-ha_create_table->ha_create // 进入存储引擎,各自实现(引擎层)ha_innobase::create|-create_table_info_t::prepare_create_table //表名预处理等|-row_mysql_lock_data_dictionary //加dict_sys的锁dict_sys_mutex_enter();|-create_table_info_t::create_table|-create_table_def|-dict_mem_table_create // malloc dict_table_t类+填列|-dict_table_add_to_cache // 加入dict_sys的hash|-row_create_table_for_mysql|-...fil_ibd_create//写IBD文件|-create_index //创建二级索引/处理外键约束等|-innobase_commit_low|-create_table_update_dict;至此已经commit, 更新一些统计信息|-row_mysql_unlock_data_dictionary //开释dict_sys的锁 dict_sys_mutex_exit();

8.0流程:

(server层)mysql_create_table|-mdl_locker.ensure_locked(db) // 同5.7 对schema上IX锁|-各种初期检讨,但不包括FRM|- rea_create_base_table|-Dictionary_client::store //将新表信息写人DD的InnoDB表|-dd::acquire_for_modification // dd_client在线程内DD缓存加入新表元数据|-ha_create_table->ha_create // 进入存储引擎,各自实现(引擎层)ha_innobase::create->innobase_basic_ddl::create_impl|-create_table_info_t::prepare_create_table //表名预处理等|-create_table_info_t::create_table// 不在此处操作dict_sys->mutex|-create_table_def|-dict_mem_table_create // malloc dict_table_t类+填列|-row_create_table_for_mysql|-...fil_ibd_create/btr_sdi_create_index //写IBD文件和个中的SDI|-dict_sys_mutex_enter() // 8.0仅在dict_table_add_to_cache前后操作锁|-dict_table_add_to_cache // 加入dict_sys的hash|-dict_sys_mutex_exit();|-create_index (回到外层SQL)trans_commit_implicit->dd::cache::Dictionary_client::commit_modified_objects

在server层进入InnoDB之前,5.7 和8.0 版本最紧张的差异是元数据的持久化存储。
5.7 版本写FRM文件,8.0 版本直接将元数据写入InnoDB表,详见下文2.2章节。
除此之外,8.0 版本代码中,对server层数据字典缓存机制进行了重构,详见下文2.3章节。
在进入InnoDB后,InnoDB表的元数据缓存构造dict_sys的持锁粒度,也在8.0 版本变得更风雅,详见下文2.4章节。

2.2 元数据持久化策略的变革

比拟上述流程,不难创造,在server层调用存储引擎接口进入InnoDB之前,MySQL 5.7 和8.0 版本分别在rea_create_table(5.7)和 rea_create_base_table(8.0) 实现了一部分持久化干系步骤。

5.7 中,server层首先写FRM文件持久化表构造,并通过检讨同名FRM文件是否存在来担保同名表不会被重复创建。

8.0 中,不再利用FRM文件,通过Dictionary_client::store->Storage_adapter::store 的调用,直接将元数据的改动写入InnoDB格式的数据字典表(DD table)中,由InnoDB引擎的能力担保这条元数据改动的事务性。
其真正持久化,是在DDL事务提交之后。
取消独立FRM文件,也避免了上文背景描述中提到的问题:DDL过程中,mysqld进程崩溃的场合,无法担保IBD和FRM文件同时被创建或清理。
比较5.7检讨同名FRM文件冲突的做法,8.0 版本由元数据锁(Metadata Lock, MDL)规避并发创同名表的场景。

2.3 DD缓存机制的变革

MySQL 8.0 版本在server层元数据缓存的最紧张改动是引入了二级缓存,新增了两种类型DD的缓存:会话私有的局部缓存Local Cache和所有会话可见的全局共享缓存Shared_dictionary_cache。

server层查询DD时,首先通过dd::cache::Dictionary_client类的接口,查询会话自身的局部缓存Local Cache。
如果在自身的Local Cache不命中,再去查询全局缓存Shared_dictionary_cache,在全局缓存中命中的DD工具将同时被加入会话的局部缓存。

当这两种缓存皆不命中时,才会去调存储引擎InnoDB的接口查询。
如果在存储引擎查询到相应的DD工具,返回的工具将同时更新到会话自身的局部缓存Local Cache和Server层的全局缓存Shared_dictionary_cache。

比拟5.7 和8.0 在server层元数据缓存机制的实现:5.7 在 server层只有一层全局的table_def_cache,在创表之前,调用get_cached_table_share进行重复性校验。

get_cached_table_share通过HashMap中表名和元数据的映射关系,查找相应表元数据的内存构造。
如果只有一层全局的元数据缓存,为了担保多线程环境下的安全,不可避免的会涉及线程间锁的竞争。
8.0 版本引入的会话级局部缓存Local Cache,命中时不用再去访问全局的缓存,能够大幅减少锁冲突的频率,提升了性能。

2.4 InnoDB的dict_sys_t的变革

在InnoDB内部,单独掩护了一套元数据信息缓存,也便是我们常说的dict_sys_t,里面掩护了当前已经在InnoDB打开的表的元数据信息。
该InnoDB的元数据信息缓存从5.7 延续到了8.0 。

创表时,InnoDB层读取Server层通报下来新表的元数据信息,在其内部创建一个对应的dict_table_t构造来掩护,然后调用dict_table_add_to_cache将该dict_table_t加入到dict_sys的hash表中。
dict_sys->mutex是InnoDB全体dict_sys的锁,8.0 在 dict_table_add_to_cache 调用的前后,才获取和开释dict_sys->mutex;而5.7 则在 ha_innobase::create的大部分流程都持有这把锁,从内存中DD表工具dict_table_t的堆内存申请、填写到commit后统计信息的更新。
这个差异影响了并发创表的效率。

2.5 Information_schema的变革

在5.7 版本,information_schema是基于临时表实现,其依赖于独立的表构造FRM文件,产生大量I/O开销,导致性能较差;而在8.0 版本,DD干系的表基于InnoDB引擎持久化存储,information_schema的定义成为基于这些DD table的视图。
比较5.7 版本,这种基于视图的做法,避免了读取FRM文件时与磁盘的交互,基于DD表的视图查询,也能充分利用优化器和DD表本身的索引提升性能。

2.6 DD变革总结

总结上文,DD从5.7版本到8.0版本的变革如表一。

表一

变革项

5.7

8.0

冗余文件

独立于IBD的FRM文件,DDL过程中crash时,两者不一致。
字符集文件db.opt、TRG触发器文件等

撤消FRM等独立文件,统一用事务性的DD表存储。

元数据持久化

表构造依赖独立的FRM文件

基于InnoDB的DD表,有事务性。

Information Schema

临时表,依赖于FRM,IO开销大

视图,基于InnoDB引擎的DD表,性能优化

Server层缓存机制

一层全局的Table_def_cache

两层:会话私有的Local Cache,全局的Shared_dictionary_cache

InnoDB DD缓存dict_sys的锁粒度

创表流程中,单个线程持锁贯穿全体创建流程,影响并发度

细粒度持锁/解锁

3.原子性DDL与DDL log表

DDL原子性由InnoDB在8.0的新能力担保,这部分能力与DD重构干系。
一方面,元数据存储在InnoDB表中,本身就担保了事务性;另一方面,在server层存储元数据到基于InnoDB的DD表完成后,后续DDL流程中干系数据文件处理的原子性。
例如,创表过程中索引的创建、IBD文件的天生,则由另一张DD表DDLlog担保。

为了担保DDL的原子性,在DDL过程中,每一个对文件修正或对干系内存工具修正的动作,都会记录在基于InnoDB引擎的DD表DDL log里。
其类定义和内存中的实例为:

class Log_DDLdict_table_t ddl_log;

DDL每一个关键步骤实行完,这张DDL log表直接记录与该已实行步骤相对应的回滚操作。
以创建一张不包含二级索引的表为例,InnoDB层会实行以下函数调用:

create_index->row_create_index_for_mysql->dict_create_index_tree_in_mem,创建完B+树索引后,会有Log_DDL::write_free_tree_log-> Log_DDL::insert_free_tree_log 的调用。

Log_DDL::write_free_tree_log会记录2条日志:一条是“创建索引”对应的回滚日志,即删除对应索引的操作;另一条是删除日志,对以上的回滚日志进行删除。

如果DDL事务终极是提交的,删除日志就会被提交,则创索引对应的回滚操作不会被实行;而如果DDL终极是被回滚的,那么删除日志本身也被回滚,而创索引对应的回滚操作就会被实行,终极该新建的索引会被回滚,以此来完成DDL真正的回滚。
如果DDL涉及到其它文件或者内存操作,都是按照相同的逻辑进行回滚日志和删除日志的记录,以确保DDL的提交和回滚之后,对应的文件和内存得到精确的清理和复位。

Log_DDL::insert_free_tree_log中的回滚日志,详细内容即“创索引”的回滚操作:与创B+树对应的操作,即开释索引对应的B+树。
在DDL log中新增的一条DDL_Record,记录了create table到一半时索引的信息:space、page、id等,实现如下:

DDL_Record record;record.set_id(id);record.set_thread_id(thread_id);record.set_type(Log_Type::FREE_TREE_LOG);record.set_space_id(index->space);record.set_page_no(index->page);record.set_index_id(index->id);{DDL_Log_Table ddl_log(trx);error = ddl_log.insert(record);}

类似的DDL log记录还有:

1. ALTER TABLE RENAME时,有Log_DDL::write_rename_table_log,分别记录新老表名。

2. 创建表空间时的Log_DDL::write_delete_space_log。

3. 上文创表过程中dict_table_add_to_cache 将InnoDB的内存DD构造存入dict_sys后,Log_DDL::write_remove_cache_log。

4. DROP TABLE时Log_DDL::write_drop_log,记录将要被drop的table id。

在事务处理的末了或在重启后crash recovery的流程中,无论事务该当提交还是回滚,server层接口 handlerton构造体的post_ddl 接口都会调用相应存储引擎的实现,进入InnoDB后的函数接口为innobase_post_ddl->Log_DDL::post_ddl。

Post_ddl步骤中,如果事务终极被提交,那么如前文所述,DDL log中的回滚日志会被彻底删除,回滚不会被实行,无需对提交前已经实行的创索引、RENAME等步骤做额外的动作。
一些场景下,文件操作日志将会被实行,例如删表操作的终极清理:比拟上文DDL log记录的命名可以创造,只有drop table的接口名Log_DDL::write_drop_log的命名办法并非“已实行步骤的回滚操作”,而是drop自己。
这是由于drop table只有在DDL事务提交时,才会真正实行删除操作,进行终极的清理;如果没有commit,删除没有真正发生时,并不须要真正地对删除进行回滚操作。

如果DDL事务终极被回滚,那么上文所述DDL log中的删除日志本身也被回滚,而DDL log中的回滚日志会被实行,根据不同的回滚类型,创建的索引会被删除,RENAME的表名会被退回老表名,存入InnoDB层元数据缓存dict_sys的内存构造将被清理。

4. MDL锁的部分变革4.1 代码架构的重构

上文所说8.0 版本对DD的重构,对元数据锁(Metadata Lock,MDL)较为直不雅观的一个改变是代码架构的重构。

8.0 在sql/dd/impl/dictionary_impl.cc 中,dd的namespace内,封装了常见的table和tablespace级别的排他、共享MDL接口,例如:server层刚进入CREATE TABLE流程时,在mysql_create_table_no_lock接口中, 对全体库加intention exclusive(IX)级别MDL锁的步骤,将其封装在类dd::Schema_MDL_locker中。

对付这些常见的表级、库级的MDL操作,5.7 版本通过MDL_REQUEST_INIT等宏管理MDL要求,这些宏的直接调用分散在各种接口的实现中,缺少统一的函数封装,可掩护性较差。
而在8.0 版本中,纵然这些dd namespace下的接口在最底层的调用仍旧为MDL_REQUEST_INIT宏不变,这种设计模式也表示了8.0 DD重构后server层对DD统一管理的思路。

4.2 MDL锁类型的拓展

enum_mdl_namespace 列举值记录了MDL锁的不同类型工具,在常规的库、表、触发器、函数等之外,8.0新增的MDL列举值包括:

SRID,ACL_CACHE,COLUMN_STATISTICS,RESOURCE_GROUPS,FOREIGN_KEY,CHECK_CONSTRAINT,BACKUP_TABLES,BACKUP_LOCK,

这些MDL的列举值细化了MDL的粒度。
例如FOREIGN_KEY列举值,在ALTER TABLE RENAME 流程中,重命名Foreign Key时,会单独对外键的名字加MDL锁;ACL_CACHE列举值是在用户鉴权发生变革的语句实行过程中持锁,此时其他新建立的连接如果拿不到ACL cache的MDL锁,则无法鉴权进行连接。

4.3 新增SDI的MDL

在8.0 版本,DD由于表构造不再依赖于server层的FRM文件。
除了server层共用的DD表之外,InnoDB还将这份信息以(Serialized dictionary information (SDI)格式存在了tablespace 的物理文件(.IBD)中。

这份SDI元数据是为了应对DD出错的情形下,能够基于单个IBD文件利用ibd2sdi工具获取表构造、规复数据。
InnoDB将SDI信息写入同IBD文件的做法,比较5.7 版本基于独立FRM文件、缺少原子性的实现办法更可靠;在DD表破坏时,单个IBD文件仍旧可以通过自带的SDI信息,规复出表构造,即表数据文件自我描述的,可以不依赖于DD解析自身(只管MyISAM在8.0 版本仍把SDI作为独立文件)。

这个新增的SDI机制,在drop table/tablespace时须要MDL锁,在事务提交时自动开释,其接口为:dd_sdi_acquire_exclusive_mdl/dd_sdi_acquire_shared_mdl。
但是,这把MDL锁不会与其它库表冲突,是由于其输入的表名和库名会被分外处理,如其库名为dummy_sdi_db,而表名利用SDI_的前缀,实现和真正的space id进行字符串拼接。

MDL因DD的重构,还有其他很多方面的变革,在本篇中不再展开。

5. 总结

本文对社区MySQL5.7 到8.0 演进过程中数据字典DD的重构(缓存,持久化),Atomic DDL的关键实现进行了剖析:在server层,通过InnoDB为引擎的数据字典表取代了FRM文件,担保了元数据存储的事务性,并通过Local Cache、Shared_dictionary_cache二级缓存,减少锁冲突,提升性能。
Atomic DDL的关键实现基于InnoDB为引擎的数据字典表DDL log,将元数据和DDL的操作存入事务性存储引擎的数据字典表中,有效担保了元数据的同等性。

6. 参考

[1] https://dev.mysql.com/blog-archive/mysql-8-0-data-dictionary-background-and-motivation/

[2] https://dev.mysql.com/blog-archive/mysql-8-0-improvements-to-information_schema/

点击关注,第一韶光理解华为云新鲜技能~

华为云博客_大数据博客_AI博客_云打算博客_开拓者中央-华为云

标签:

相关文章

微信跳转微信支付便捷支付体验的秘密武器

移动支付已成为人们日常生活中不可或缺的一部分。作为我国领先的社交平台,微信支付凭借其便捷、安全的支付方式,深受广大用户的喜爱。而微...

SEO优化 2025-02-18 阅读0 评论0

探寻会计科目代码背后的奥秘分类与

会计科目代码是会计信息系统中不可或缺的组成部分,它将企业的经济活动进行分类和归纳,为会计核算、财务分析和决策提供重要依据。本文将从...

SEO优化 2025-02-18 阅读0 评论0