为了实现DDL原子性,MySQL 8.0 使用Innodb表存储相关的数据字典信息,这些数据字典表默认不可见,查看方法参照

SL的相关源码都存放在中,目录下dd_xxx.cc / dd_xxx.h,为SL数据字典操作的入口。以创建表以及表数据字典对象为例,dd_table.ccdd::create_table创建一个server层的,表的,数据字典对象。其类型为dd::Table,定义在sql/dd/types/table.h中。而dd::Table真正的实现放在sql/dd/impl/table_impl.h / sql/dd/impl/table_impl.cc中。

sql/dd/impl/cache中主要是操作数据字典缓存,大都是模板类或模板方法,模板成员为SL中的各种数据字典对象(如dd::Table)。sql/dd/impl/cache/dictionary_client.cc中实现了缓存对象的操作,包括从缓存获取、存取、丢弃等。

SE的相关源码主要存放在storage/innobase/dict/中,主要的innodb层的数据字典内存对象是dict_index_t, dict_table_t

由于篇幅有限,不能将所有的细节都阐述清楚,下面以创建一张基础表为例,简单介绍存数据字典的调用过程:

即调用dd::Table_impl::store_children会同时将indexesforeign_keys等数据字典表更新

介绍MySQL 8.0 原子DDL的资料有很多,这里以创建表为例,简单阐述创建表过程与原子DDL相关的关键流程。

为了实现原子DDL,MySQL 8.0借助mysql.innodb_ddl_log,将DDL过程划分成以下四个步骤:

  1. Prpare: 写DDL logs到mysql.innodb_ddl_log,如对应一个per-file-table建表操作,会写入write_delete_space_log, write_remove_cache_log, write_free_tree_log三条记录。
  2. Perform: 执行DDL操作
  3. Commit: 更新数据字典并且提交数据字典事务
  4. Post-DDL: 重放并且移除mysql.innodb_ddl_log对应的DDL logs,重命名和移除大数据文件都放在这个过程中完成。

MySQL 8.0 借助Innodb的事务特性完成DDL操作。所有的DDL操作都会起一个innodb层的事务,对数据字典进行增删查改的操作,如果DDL事务执行失败,则进行回滚,这部分与正常事务是一致的。但由于DDL操作涉及文件操作,MySQL 8.0 通过DDL logs来辅助实现原子性,相关源码主要在storage/innobase/log/log0ddl.cc。当创建一张数据表的时候,需要写一条删除索引树的记录。假设DDL操作的事务为TRX_A,则在write_free_tree_log中另起一个事务插入一条记录free tree log并马上提交,随后以TRX_A的身份将这条记录删除。当事务commit的时候会有两种情况:

  1. DDL事务TRX_A回滚,则mysql.innodb_ddl_log中存在一条free tree log,重放删除对应的数据文件,并移除这条记录(Log_DDL::replay)

对于drop操作,其处理逻辑也是类似。但write_free_tree_log中会以DDL事务TRX_A的身份写入一条free tree log,则在Post-DDL中会真正地将表删掉并移除对应的记录。

Online DDL指的是在DDL期间,允许用户进行DML操作。并非所有DDL操作都支持Online DDL,官方文档 https://dev.mysql.com/doc/refman/8.0/en/innodb-online-ddl-operations.html 详细展示了所有DDL在执行期间是否允许进行DML操作。

这里阐述rebuild表时,Online DDL的关键流程():

  1. 持有MDL_SHARED_UPGRADABLE锁,检测表时是否存在
  2. 升级到MDL_EXCLUSIVE锁,禁止读写
  3. 更新数据字典对象
  4. 分配row_log对象记录增量
  5. 生成新表
  6. 降级为MDL_SHARED_UPGRADABLE,允许对原表进行读写(wait_while_table_is_used
  7. 用DDL事务的上下文,扫描老表的中,对该事务可见的数据,并用merge排序,最后插入到新表,详细见row_merge_build_indexes。PS:由于使用到merge外排序,所以会受到innodb_sort_buffer_size的限制。
  8. 进入commit阶段,升级到MDL_EXCLUSIVE锁,禁止读写
  9. 在新表中apply row_log里的增量(row_log_apply)
  10. 更新innodb的数据字典表
  11. 提交DDL事务
  12. 重命名新表的ibd文件

值得注意的是:

  1. 这里row_log与mysql.innodb_ddl_log不一样,前者的源码主要在storage/innobase/row/row0log.cc,后者的相关代码在。row_log是一个append形式的增量日志,里面有三种类型的记录,insert/update类型的如下所示:

  2. 上述第10步之后,即将锁升级为互斥锁,则可以认为已经没有事务在操作原表,否则无法升锁。所以在后续apply logs的时候,当发现delete类型的记录,则直接对新表进行purge,而不是标记为deleted mark。

  3. 了解上述inpalce rebuild类型的流程后,可以对官方DDL操作是否能支持online ddl有直观的理解。如drop primary key不支持online ddl,而drop primary的同时add primary却支持online ddl,两种方式都需要rebuild。但是对于前者,采用的是copy的方式而不是inplace,主要原因是新表没有主键,这种情况下innodb会默认为其申请一个隐藏的rowid作为主键,当apply logs的时候,由于log中的增量没有rowid信息,所以没法进行下去。