8.0以前并无统一的数据字典dd,server层和引擎层各有一套元数据,sever层的元数据包括(.frm,.opt,.par,.trg等),用于存储表定义,分区表定义,触发器定义等信息;innodb层也有本身一套元数据,包括表信息,索引信息等,这两套元数据并无机制保证一致性,这就致使了在异常状况下可能存在元数据不一致问题,一种典型场景下,删表操做,sever层的frm已经成功删除了,但引擎层数据字典并无更新,致使再建重名表失败的问题。一样的,好比drop table t1,t2;可能出现只删除了t1,而t2仍然存在等问题。html
8.0的一个重要工做是将数据字典统一,独立了DD(数据字典)模块,废弃了server层的元数据,将innodb的元数据抽象出一条DD接口供server层和innnodb层公用。在DD的基础上,引入了DDL的原子性特性,确保DDL操做要么全作,要么全不作的能力。实现这一套逻辑的关键点在于将ddl涉及到的修改,包括dd数据字典修改,引擎层的修改(建立文件,初始化tablespace,建立btree等)和写binlog做为一个“事务”,利用事务的原子性特色来保证ddl操做的原子性。mysql
实现原子性的关键在于确保dd数据字典修改,引擎层的修改和写binlog是一个事务。MySQL已有的XA事务机制能有效保证DML事务和binlog的一致性。而ddl数据字典也是经过innodb引擎存储,所以作到dd数据字典修改和binlog一致是容易的;那么还须要解决的一个问题是,dd数据字典和引擎层修改的一致性,引擎层的修改并不都是记redo的,好比建立文件,rename文件名,或者清理cache等,没法简单地经过XA机制解决问题,所以8.0还引入了一套DDL_LOG机制。具体而言,就是将不记redo的一些操做,经过记日志的方式写入到ddl_log表中,而这个表是innodb引擎表,经过保证ddl_log数据与dd数据字典修改达成一致,而最终解决dd数据字典修改,引擎层的修改和写binlog一致性问题。sql
4.DDL操做实现逻辑bash
引入ddl_log表后,ddl操做在原有的基础上有一些变化,主要有两点,一点是在执行ddl的过程当中,会记录ddl操做到ddl_log表中;另外一点是新增了一个post_ddl阶段,ddl事务提交后,作一些ddl的收尾动做,好比drop-table,真正的删除物理文件是在post-ddl阶段作的。post-ddl作的事情主要就是,读取ddl-log内容,进行回放执行。ddl操做类型以下:post
enum class Log_Type : uint32_t { /** Smallest log type */ SMALLEST_LOG = 1, /** Drop an index tree */ FREE_TREE_LOG = 1, /** Delete a file */ DELETE_SPACE_LOG, /** Rename a file */ RENAME_SPACE_LOG, /** Drop the entry in innodb_dynamic_metadata */ DROP_LOG, /** Rename table in dict cache. */ RENAME_TABLE_LOG, /** Remove a table from dict cache */ REMOVE_CACHE_LOG, /** Alter Encrypt a tablespace */ ALTER_ENCRYPT_TABLESPACE_LOG, /** Biggest log type */ BIGGEST_LOG = ALTER_ENCRYPT_TABLESPACE_LOG };
经过innodb_print_ddl_logs开关,能够看到ddl过程当中写入到innodb_ddl_log表中的内容。下面会以几个典型的ddl操做产生的ddl_log来讲明如何保证ddl的原子性。ui
4.1 create tablespa
语句:create table dd_tt(id int primary key, c1 int); 日志
[InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=352, thread_id=23, space_id=71, old_file_path=./mysql/dd_tt.ibd] [InnoDB] DDL log delete : 352 [InnoDB] DDL log insert : [DDL record: REMOVE CACHE, id=353, thread_id=23, table_id=1128, new_file_path=mysql/dd_tt] [InnoDB] DDL log delete : 353 [InnoDB] DDL log insert : [DDL record: FREE, id=354, thread_id=23, space_id=71, index_id=231, page_no=4] [InnoDB] DDL log delete : 354 [InnoDB] DDL log post ddl : begin for thread id : 23 [InnoDB] DDL log post ddl : end for thread id : 23
说明:code
1.全部insert操做都是一个单独的事务,对应的逆向delete操做是整个ddl事务的一部分。orm
2.insert操做记录的是文件操做的逆向操做,好比建table_space,逆向操做就是delete_space_log。
3.若是ddl事务最终成功,那么全部逆向delete操做也最终生效,ddl_log日志被正常清理;若是ddl事务执行过程当中失败(好比实例crash),那么delete操做回滚,ddl_log表中残留3条insert_log,recover时,replay这些ddl_log,便可以清理ddl过程当中产生的垃圾。
4.crash-recovery时,若binlog已经落盘,则对应的ddl事务处于prepare状态,那么最终事务要提交,ddl_log被清理干净;若binlog没有落盘,则ddl事务须要回滚,ddl_log表中残留3条记录,在故障恢复结束后,须要replay这些记录,实际上就是建文件,建立btree等逆向操做,确保回滚后是干净的。
4.2 drop table
语句:drop table dd_tt;
[InnoDB] DDL log insert : [DDL record: DROP, id=355, thread_id=23, table_id=1128] [InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=356, thread_id=23, space_id=71, old_file_path=./mysql/dd_tt.ibd] [InnoDB] DDL log post ddl : begin for thread id : 23 [InnoDB] DDL log replay : [DDL record: DELETE SPACE, id=356, thread_id=23, space_id=71, old_file_path=./mysql/dd_tt.ibd] [InnoDB] DDL log replay : [DDL record: DROP, id=355, thread_id=23, table_id=1128] [InnoDB] DDL log post ddl : end for thread id : 23
说明:对于drop操做而言,执行过程当中只是操做ddl_log,并不作真正的drop物理表操做。在post-ddl阶段,会读取ddl_log表中的记录并replay,作真正的删除动做。若是执行过程当中crash了,那么整个ddl事务会回滚,这其中也包含ddl_log中的内容也会回滚,那么整个drop操做就至关于没发生同样。
4.3 add index
语句:alter table dd_tt add index idx_c1(c1);
[InnoDB] DDL log insert : [DDL record: FREE, id=360, thread_id=23, space_id=72, index_id=233, page_no=5] [InnoDB] DDL log delete : 360 [InnoDB] DDL log post ddl : begin for thread id : 23 [InnoDB] DDL log post ddl : end for thread id : 23
说明: 建索引与建表相似,insert操做部分是一个事务,单独提交,配套会记录一个delete操做,这个操做是整个ddl事务的一部分,事务若是最终提交,那么ddl-log内容被删除;若是事务最终回滚,那么ddl-log中会残留一条FREE-log,经过replay则能够清理建好的索引,达到回滚的效果。
4.4 drop index
语句:alter table dd_tt drop index idx_c1;
[InnoDB] DDL log insert : [DDL record: FREE, id=361, thread_id=23, space_id=72, index_id=233, page_no=5]
[InnoDB] DDL log post ddl : begin for thread id : 23
[InnoDB] DDL log replay : [DDL record: FREE, id=361, thread_id=23, space_id=72, index_id=233, page_no=5]
[InnoDB] DDL log post ddl : end for thread id : 23
说明:
与drop table相似,执行过程当中只记录日志,在post-ddl阶段才进行真正的删除操做。
4.5 add column
语句:alter table dd_tt add column c2 int;
[InnoDB] DDL log post ddl : begin for thread id : 23 [InnoDB] DDL log post ddl : end for thread id : 23
说明:
8.0加列是instant-ddl,只修改元数据,与dml事务相似,不依赖ddl-log保证原子性。
4.6 drop column
语句:alter table dd_tt drop column c2;
语句分解:
1.prepare阶段:
create table #sql-ib1129-2815969725;
[InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=362, thread_id=23, space_id=73, old_file_path=./mysql/#sql-ib1129-2815969725.ibd] [InnoDB] DDL log delete : 362 [InnoDB] DDL log insert : [DDL record: REMOVE CACHE, id=363, thread_id=23, table_id=1130, new_file_path=mysql/#sql-ib1129-2815969725] [InnoDB] DDL log delete : 363 [InnoDB] DDL log insert : [DDL record: FREE, id=364, thread_id=23, space_id=73, index_id=234, page_no=4] [InnoDB] DDL log delete : 364
2.peform阶段:nothing about ddl-log
3.commit阶段:
3.1 alter table dd_tt rename to #sql-ib1130-2815969726;
[InnoDB] DDL log insert : [DDL record: DROP, id=365, thread_id=23, table_id=1129]
[InnoDB] DDL log insert : [DDL record: RENAME SPACE, id=366, thread_id=23, space_id=72, old_file_path=./mysql/#sql-ib1130-2815969726.ibd, new_file_path=./mysql/dd_tt.ibd] [InnoDB] DDL log delete : 366 [InnoDB] DDL log insert : [DDL record: RENAME TABLE, id=367, thread_id=23, table_id=1129, old_file_path=mysql/#sql-ib1130-2815969726, new_file_path=mysql/dd_tt] [InnoDB] DDL log delete : 367
逆向操做:alter table mysql/#sql-ib1130-2815969726 rename to dd_tt;
3.2 alter table #sql-ib1129-2815969725 rename to dd_tt;
[InnoDB] DDL log insert : [DDL record: RENAME SPACE, id=368, thread_id=23, space_id=73, old_file_path=./mysql/dd_tt.ibd, new_file_path=./mysql/#sql-ib1129-2815969725.ibd] [InnoDB] DDL log delete : 368 [InnoDB] DDL log insert : [DDL record: RENAME TABLE, id=369, thread_id=23, table_id=1130, old_file_path=mysql/dd_tt, new_file_path=mysql/#sql-ib1129-2815969725] [InnoDB] DDL log delete : 369
逆向操做:alter table dd_tt rename to mysql/#sql-ib1129-2815969725;
[InnoDB] DDL log insert : [DDL record: RENAME SPACE, id=368, thread_id=23, space_id=73, old_file_path=./mysql/dd_tt.ibd, new_file_path=./mysql/#sql-ib1129-2815969725.ibd] [InnoDB] DDL log delete : 368 [InnoDB] DDL log insert : [DDL record: RENAME TABLE, id=369, thread_id=23, table_id=1130, old_file_path=mysql/dd_tt, new_file_path=mysql/#sql-ib1129-2815969725] [InnoDB] DDL log delete : 369
仅仅记录操做,在post-ddl阶段才作清理。
post-ddl阶段:
drop table #sql-ib1130-2815969726;
[InnoDB] DDL log insert : [DDL record: RENAME SPACE, id=368, thread_id=23, space_id=73, old_file_path=./mysql/dd_tt.ibd, new_file_path=./mysql/#sql-ib1129-2815969725.ibd] [InnoDB] DDL log delete : 368 [InnoDB] DDL log insert : [DDL record: RENAME TABLE, id=369, thread_id=23, table_id=1130, old_file_path=mysql/dd_tt, new_file_path=mysql/#sql-ib1129-2815969725] [InnoDB] DDL log delete : 369
说明:drop column是copy类型的ddl,基本逻辑是新建一张临时表,拷贝数据,最后再进行一次rename操做。主要包括4个阶段:
1.prepare阶段:建临时表的过程与建表过程的ddl-log操做相似,insert-log做为单独事务直接提交,delete-log是整个事务的一部分。
这个阶段若是出现异常,ddl-log表中残留了逆操做记录,crash-recovery时,能够在replay实现清理。
2.peform阶段: 拷贝数据结束,实现online-ddl逻辑。
3.拷贝数据结束后,须要进行rename交换表名操做。
1)DROP,删除临时表
2)RENAME SPACE/TABLE 将./mysql/#sql-ib1130-2815969726.ibd 重命名为dd_tt.idb
3)REANAME SPACE/TABLE 将dd_tt.idb重名为/#sql-ib1129-2815969725.idb
4)记录删除旧表sql-ib1130-2815969726.ibd操做,post-ddl阶段作真正的删除。
若是这个阶段出现异常,一样的insert-log单独一个事务,delete做为整个事务的一部分,insert-log会残留在ddl-log表中,经过replay能够作清理,还原dd_tt的数据,并清理临时表#sql-ib1130-2815969726.ibd。
4.post-ddl阶段:
1).物理删除旧文件./mysql/#sql-ib1130-2815969726.ibd
2).清理mysql.innodb_dynamic_metadata中相关信息。
须要注意的是,因为ddl-log表存放的内容实际上逆向操做,因此搜集ddl-log时,其实是逆序搜集回放的。
4.7 truncate table
语句:truncate table dd_tt;
语句分解:
1.rename dd_tt to #sql-ib1130-2815969727;
[InnoDB] DDL log insert : [DDL record: RENAME SPACE, id=372, thread_id=23, space_id=73, old_file_path=./mysql/#sql-ib1130-2815969727.ibd, new_file_path=./mysql/dd_tt.ibd [InnoDB] DDL log delete : 372
2.drop table #sql-ib1130-2815969727;
[InnoDB] DDL log insert : [DDL record: DROP, id=373, thread_id=23, table_id=1130] [InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=374, thread_id=23, space_id=73, old_file_path=./mysql/#sql-ib1130-2815969727.ibd]
3.create table dd_tt;
[InnoDB] DDL log insert : [DDL record: DELETE SPACE, id=375, thread_id=23, space_id=74, old_file_path=./mysql/dd_tt.ibd] [InnoDB] DDL log delete : 375 [InnoDB] DDL log insert : [DDL record: REMOVE CACHE, id=376, thread_id=23, table_id=1131, new_file_path=mysql/dd_tt] [InnoDB] DDL log delete : 376 [InnoDB] DDL log insert : [DDL record: FREE, id=377, thread_id=23, space_id=74, index_id=235, page_no=4] [InnoDB] DDL log delete : 377 [InnoDB] DDL log post ddl : begin for thread id : 23 [InnoDB] DDL log replay : [DDL record: DELETE SPACE, id=374, thread_id=23, space_id=73, old_file_path=./mysql/#sql-ib1130-2815969727.ibd] [InnoDB] DDL log replay : [DDL record: DROP, id=373, thread_id=23, table_id=1130] [InnoDB] DDL log post ddl : end for thread id : 23
说明:
1.将dd_tt重命名为sql-ib1130-2815969727
2.标记sql-ib1130-2815969727表删除,post-ddl阶段才真正删除
3.新建表dd_tt,一样的insert操做是做为单独事务提交,delete操做是整个事务的一部分,若是回滚,最终残留了insert操做,经过replay动做清理。
5.1 create-table
Sql_cmd_create_table::execute -->mysql_create_table -->mysql_create_table_no_lock -->create_table_impl -->rea_create_base_table -->ha_create_table -->ha_create -->ha_innobase::create -->innobase_basic_ddl::create_impl -->create_table_info_t::create_table { ...... } -->trans_commit_implicit -->ha_commit_trans -->MYSQL_BIN_LOG::prepare -->ha_prepare_low //全部事务引擎prepare { binlog_prepare innobase_xa_prepare } -->MYSQL_BIN_LOG::commit -->MYSQL_BIN_LOG::ordered_commit -->MYSQL_BIN_LOG::process_flush_stage_queue -->MYSQL_BIN_LOG::flush_thread_caches -->binlog_cache_mngr::flush -->binlog_cache_data::flush -->MYSQL_BIN_LOG::write_gtid -->Log_event::write -->MYSQL_BIN_LOG::Binlog_ofile::write //写binlog-gtid -->MYSQL_BIN_LOG::write_cache --> MYSQL_BIN_LOG::do_write_cache -->Binlog_cache_storage::copy_to -->stream_copy -->Binlog_event_writer::write -->MYSQL_BIN_LOG::Binlog_ofile::write //写binlog-ddl语句 -->MYSQL_BIN_LOG::sync_binlog_file -->MYSQL_BIN_LOG::process_commit_stage_queue -->ha_commit_low { binlog_commit innobase_commit -->trx_commit_for_mysql -->trx_commit -->trx_commit_low -->trx_commit_in_memory -->trx_undo_insert_cleanup } -->innobase_post_ddl(ht->post_ddl(thd)) -->Log_DDL::post_ddl -->replay_by_thread_id
-->create_table_info_t::create_table -->create_table_def -->dict_mem_table_create //构造innodb内存是字典内存对象 -->row_create_table_for_mysql -->dict_build_table_def -->dict_build_tablespace_for_table -->新建xxx.idb文件 -->Log_DDL::write_delete_space_log { -->Log_DDL::insert_delete_space_log -->trx_start_internal //内部开启事务,单独提交。 -->构造DDL_Record(DELETE_SPACE_LOG) -->DDL_Log_Table::insert(写入物理B-Tree) -->Log_DDL:delete_by_id //删除ddl_log操做,做为ddl事务的一部分。 } -->fil_ibd_create -->初始化segment,extent,page -->Log_DDL::write_remove_cache_log -->Log_DDL::insert_remove_cache_log -->Log_DDL::delete_by_id -->create_index(主表,二级索引) -->dict_create_index_tree_in_mem -->btr_create -->Log_DDL::write_free_tree_log -->Log_DDL::insert_free_tree_log -->Log_DDL:delete_by_id
crash-recovery -->ha_post_recover -->post_recover_handlerton -->innobase_post_recover -->Log_DDL::recover -->Log_DDL::replay_all -->Log_DDL::replay { replay_delete_space_log replay_remove_cache_log replay_free_tree_log ...... } -->delete_by_ids -->DDL_Log_Table::remove
5.2 drop table
mysql_rm_table -->mysql_rm_table_no_locks -->drop_base_table -->ha_delete_table -—>handler::ha_delete_table -->ha_innobase::delete_table -->innobase_basic_ddl::delete_impl -->row_drop_table_for_mysql -->Log_DDL::write_drop_log // 记录删innodb_dynamic_metadata日志 -—>Log_DDL::write_delete_space_log // 记录删ibd日志 -->dd::drop_table -->dd::cache::Dictionary_client::drop<dd::Table> -->dd::cache::Storage_adapter::drop<dd::Table> -->dd::sdi::drop -->innobase_post_ddl -->Log_DDL::post_ddl -->Log_DDL::replay_by_thread_id -->Log_DDL::replay —>Log_DDL::replay_delete_space_log // post-ddl 真正删除innodb_dynamic_metadata —>Log_DDL::replay_drop_log // post-ddl 真正删除ibd -->delete_by_ids -->DDL_Log_Table::remove
drop table时,只记录删除动做日志,这些日志做为事务的总体的一部分,若是最终事务提交,那么post_ddl阶段会读取日志真正删除;若是事务回滚,那么ddl_log也会做为事务的一部分而回滚。
https://dev.mysql.com/worklog/task/?id=9045
https://dev.mysql.com/worklog/task/?id=9173
https://dev.mysql.com/worklog/task/?id=9175
https://dev.mysql.com/worklog/task/?id=9525
https://dev.mysql.com/worklog/task/?id=9536
原文出处:https://www.cnblogs.com/cchust/p/11099450.html