为了实现DDL原子性,MySQL 8.0 使用Innodb表存储相关的数据字典信息,这些数据字典表默认不可见,查看方法参照https://dev.mysql.com/doc/refman/8.0/en/data-dictionary-schema.html
SL的相关源码都存放在中,目录下dd_xxx.cc / dd_xxx.h
,为SL数据字典操作的入口。以创建表以及表数据字典对象为例,dd_table.cc
中dd::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
会同时将indexes
、foreign_keys
等数据字典表更新
介绍MySQL 8.0 原子DDL的资料有很多,这里以创建表为例,简单阐述创建表过程与原子DDL相关的关键流程。
为了实现原子DDL,MySQL 8.0借助mysql.innodb_ddl_log
,将DDL过程划分成以下四个步骤:
- Prpare: 写DDL logs到
mysql.innodb_ddl_log
,如对应一个per-file-table建表操作,会写入write_delete_space_log
,write_remove_cache_log
,write_free_tree_log
三条记录。 - Perform: 执行DDL操作
- Commit: 更新数据字典并且提交数据字典事务
- 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的时候会有两种情况:
- 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,官方文档 详细展示了所有DDL在执行期间是否允许进行DML操作。
这里阐述rebuild表时,Online DDL的关键流程():
- 持有
MDL_SHARED_UPGRADABLE
锁,检测表时是否存在 - 升级到
MDL_EXCLUSIVE
锁,禁止读写 - 更新数据字典对象
- 分配row_log对象记录增量
- 生成新表
- 降级为
MDL_SHARED_UPGRADABLE
,允许对原表进行读写(wait_while_table_is_used
) - 用DDL事务的上下文,扫描老表的中,对该事务可见的数据,并用merge排序,最后插入到新表,详细见
row_merge_build_indexes
。PS:由于使用到merge外排序,所以会受到innodb_sort_buffer_size
的限制。 - 进入commit阶段,升级到
MDL_EXCLUSIVE
锁,禁止读写 - 在新表中apply row_log里的增量(row_log_apply)
- 更新innodb的数据字典表
- 提交DDL事务
- 重命名新表的ibd文件
值得注意的是:
这里row_log与
mysql.innodb_ddl_log
不一样,前者的源码主要在storage/innobase/row/row0log.cc
,后者的相关代码在。row_log是一个append形式的增量日志,里面有三种类型的记录,insert/update类型的如下所示:上述第10步之后,即将锁升级为互斥锁,则可以认为已经没有事务在操作原表,否则无法升锁。所以在后续apply logs的时候,当发现delete类型的记录,则直接对新表进行purge,而不是标记为deleted mark。
- 了解上述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信息,所以没法进行下去。