首页 > 数据库 >解读MySQL8.0数据字典重构源码

解读MySQL8.0数据字典重构源码

时间:2024-09-27 21:47:01浏览次数:1  
标签:重构 create 源码 MySQL8.0 DDL InnoDB table DD dict

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

1.背景介绍

在MySQL5.7 版本的使用实践过程中,我们很容易遇到DDL崩溃后导致数据不一致的问题,具体场景描述如下:

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

这个问题的实质是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里。其类定义和内存中的实例为:

1.    class Log_DDL 
2.    dict_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等,实现如下:

1.    DDL_Record record;  
2.    record.set_id(id);  
3.    record.set_thread_id(thread_id);  
4.    record.set_type(Log_Type::FREE_TREE_LOG);  
5.    record.set_space_id(index->space);  
6.    record.set_page_no(index->page);  
7.    record.set_index_id(index->id);  
8.      
9.    {  
10.      DDL_Log_Table ddl_log(trx);  
11.      error = ddl_log.insert(record);  
12.    }  

类似的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枚举值包括:

1.    SRID,  
2.    ACL_CACHE,  
3.    COLUMN_STATISTICS,  
4.    RESOURCE_GROUPS,  
5.    FOREIGN_KEY,  
6.    CHECK_CONSTRAINT,  
7.    BACKUP_TABLES,  
8.    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/

点击关注,第一时间了解华为云新鲜技术~

标签:重构,create,源码,MySQL8.0,DDL,InnoDB,table,DD,dict
From: https://www.cnblogs.com/huaweiyun/p/18436638

相关文章

  • 基于C#图书管理系统 网上图书预约系统的设计(源码+文档)
    目录一.研究目的1.1背景及意义1.2主要研究思路和方法二.需求分析三.数据库设计四.页面展示五.免费源码获取一.研究目的1.1背景及意义  21世纪初,人类社会继工业文明之后,进入知识经济与信息时代。高新技术的迅猛发展,信息技术的广泛应用,全球经济趋于一体化。在新经......
  • 基于Java+Springboot+Vue开发的健身房管理系统源码+参考文章1.3万字
    项目简介该项目是基于Java+Springboot+Vue开发的健身房管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的健身房管理系统项目,大学生可以在实践中学习和提升......
  • 基于Java+Springboot+Vue开发的家具管理系统源码+开发文档
    项目简介该项目是基于Java+Springboot+Vue开发的家具管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的家具管理系统项目,大学生可以在实践中学习和提升自己......
  • 基于Java+Springboot+Vue开发的网上商城管理系统源码+文章
    项目简介该项目是基于Java+Springboot+Vue开发的网上商城管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的网上商城管理系统项目,大学生可以在实践中学习和......
  • 基于Java+Springboot+Vue开发的医院门诊预约挂号系统源码+参考文章1.2万字
    项目简介该项目是基于Java+Springboot+Vue开发的医院门诊预约挂号系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Java的门诊预约挂号管理系统项目,大学生可以在实践......
  • 基于Springboot+Vue的二手闲置交易系统 (含源码数据库)
    1.开发环境开发系统:Windows10/11架构模式:MVC/前后端分离JDK版本:JavaJDK1.8开发工具:IDEA数据库版本:mysql5.7或8.0数据库可视化工具:navicat服务器:SpringBoot自带apachetomcat主要技术:Java,Springboot,mybatis,mysql,vue2.视频演示地址3.功能在这个......
  • 基于Springboot+Vue的半成品配菜平台设计与实现(含源码数据库)
    1.开发环境开发系统:Windows10/11架构模式:MVC/前后端分离JDK版本:JavaJDK1.8开发工具:IDEA数据库版本:mysql5.7或8.0数据库可视化工具:navicat服务器:SpringBoot自带apachetomcat主要技术:Java,Springboot,mybatis,mysql,vue2.视频演示地址3.功能这个系......
  • 【含文档】基于Springboot+Vue的高校大学生心理咨询管理系统(含源码+数据库+lw)
    1.开发环境开发系统:Windows10/11架构模式:MVC/前后端分离JDK版本:JavaJDK1.8开发工具:IDEA数据库版本:mysql5.7或8.0数据库可视化工具:navicat服务器:SpringBoot自带apachetomcat主要技术:Java,Springboot,mybatis,mysql,vue2.视频演示地址3.功能系统主......
  • 【开题报告】基于django+vue旅游景点推荐系统(论文+源码) 计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着旅游业的蓬勃发展,人们对旅游体验的需求日益多样化与个性化。在信息爆炸的时代,如何高效地筛选出符合个人兴趣的旅游景点,成为游客出行前......
  • Mysql8.0启动时出现ERROR: Different lower_case_table_names settings for server ('
    分析:出现这个原因数据库启动后,调整lower_case_table_names参数导致的这个问题。mysql8.0之后,lower_case_table_names配置必须在安装好MySQL后,初始化mysql配置时才有效。一旦mysql启动后,再设置是无效的,而且启动报错。lower_case_table_names=1表示mysql是不区分大小写的......