zoukankan      html  css  js  c++  java
  • MySQL8.0 DDL原子性特性

    1. DDL原子性概述

    8.0之前并没有统一的数据字典dd,server层和引擎层各有一套元数据,sever层的元数据包括(.frm,.opt,.par,.trg等),用于存储表定义,分区表定义,触发器定义等信息;innodb层也有自己一套元数据,包括表信息,索引信息等,这两套元数据并没有机制保证一致性,这就导致了在异常情况下可能存在元数据不一致问题,一种典型场景下,删表操作,sever层的frm已经成功删除了,但引擎层数据字典并没有更新,导致再建重名表失败的问题。同样的,比如drop table t1,t2;可能出现只删除了t1,而t2仍然存在等问题。

    8.0的一个重要工作是将数据字典统一,独立了DD(数据字典)模块,废弃了server层的元数据,将innodb的元数据抽象出一条DD接口供server层和innnodb层公用。在DD的基础上,引入了DDL的原子性特性,确保DDL操作要么全做,要么全不做的能力。实现这一套逻辑的关键点在于将ddl涉及到的修改,包括dd数据字典修改,引擎层的修改(创建文件,初始化tablespace,创建btree等)和写binlog作为一个“事务”利用事务的原子性特点来保证ddl操作的原子性。

    2.DDL原子性实现原理

    实现原子性的关键在于确保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一致性问题。

    3.DD引入前后对比

       

                                                                        

    4.DDL操作实现逻辑

    引入ddl_log表后,ddl操作在原有的基础上有一些变化,主要有两点,一点是在执行ddl的过程中,会记录ddl操作到ddl_log表中;另一点是新增了一个post_ddl阶段,ddl事务提交后,做一些ddl的收尾动作,比如drop-table,真正的删除物理文件是在post-ddl阶段做的。post-ddl做的事情主要就是,读取ddl-log内容,进行回放执行。ddl操作类型如下:

    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的原子性。

    4.1 create table

    语句: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 

    说明:

    1.所有insert操作都是一个单独的事务,对应的逆向delete操作是整个ddl事务的一部分。

    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.DDL操作代码堆栈

    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

  • 相关阅读:
    zzuli--2134: 维克兹的进制转换(规律)
    hdu--1316--How Many Fibs?(java大数)
    NYOJ--517--最小公倍数(大数打表)
    NYOJ--513--A+B Problem IV(大数)
    NYOJ--45--棋盘覆盖(大数)
    NYOJ--114--某种序列(大数)
    HAUT--1262--魔法宝石(暴力)
    NYOJ--1276--机器设备(河南省第九届省赛,简单的bfs)
    hdu--1429--胜利大逃亡(续) (bfs+状态压缩)
    NYOJ--541--最强DE 战斗力(递推)
  • 原文地址:https://www.cnblogs.com/cchust/p/11099450.html
Copyright © 2011-2022 走看看