MySQL online ddl原理


背景

     dba的日常工作肯定有一項是ddl變更,ddl變更會鎖表,這個可以說是dba心中永遠的痛,特別是執行ddl變更,導致庫上大量線程處於“Waiting for meta data lock”狀態的時候。因此mysql 5.6的online ddl特性是dba們最期待的新特性,這個特性解決了執行ddl鎖表的問題,保證了在進行表變更時,不會堵塞線上業務讀寫,保障在變更時,庫依然能正常對外提供訪問。網上關於online ddl的文章很多,但涉及原理的很少,都是介紹語法之類的,本文將詳細介紹online ddl的原理,知其然,更要知其所以然。

ddl實現方式

      5.6 online ddl推出以前,執行ddl主要有兩種方式copy方式和inplace方式,inplace方式又稱為(fast index creation)。相對於copy方式,inplace方式不拷貝數據,因此較快。但是這種方式僅支持添加、刪除索引兩種方式,而且與copy方式一樣需要全程鎖表,實用性不是很強。下面以加索引為例,簡單介紹這兩種方式的實現流程。

   copy方式

   (1).新建帶索引的臨時表

   (2).鎖原表,禁止DML,允許查詢

   (3).將原表數據拷貝到臨時表(無排序,一行一行拷貝)

   (4).進行rename,升級字典鎖,禁止讀寫

   (5).完成創建索引操作

   inplace方式

   (1).新建索引的數據字典

   (2).鎖表,禁止DML,允許查詢

   (3).讀取聚集索引,構造新的索引項,排序並插入新索引

   (4).等待打開當前表的所有只讀事務提交

   (5).創建索引結束

online ddl實現

      online方式實質也包含了copy和inplace方式,對於不支持online的ddl操作采用copy方式,比如修改列類型,刪除主鍵,修改字符集等,這些操作都會導致記錄格式發生變化,無法通過簡單的全量+增量的方式實現online;對於inplace方式,mysql內部以“是否修改記錄格式”為基准也分為兩類,一類需要重建表(重新組織記錄),比如optimize table、添加索引、添加/刪除列、修改列NULL/NOT NULL屬性等;另外一類是只需要修改表的元數據,比如刪除索引、修改列名、修改列默認值、修改列自增值等。Mysql將這兩類方式分別稱為rebuild方式和no-rebuild方式。更多關於哪些DDL是否可以inplace的內容可以參考官方文檔:http://dev.mysql.com/doc/refman/5.6/en/innodb-create-index-overview.html。online ddl主要包括3個階段,prepare階段,ddl執行階段,commit階段,rebuild方式比no-rebuild方式實質多了一個ddl執行階段,prepare階段和commit階段類似。下面將主要介紹ddl執行過程中三個階段的流程。

Prepare階段:

  1. 創建新的臨時frm文件
  2. 持有EXCLUSIVE-MDL鎖,禁止讀寫
  3. 根據alter類型,確定執行方式(copy,online-rebuild,online-norebuild)
  4. 更新數據字典的內存對象
  5. 分配row_log對象記錄增量
  6. 生成新的臨時ibd文件

ddl執行階段:

  1. 降級EXCLUSIVE-MDL鎖,允許讀寫
  2. 掃描old_table的聚集索引每一條記錄rec
  3. 遍歷新表的聚集索引和二級索引,逐一處理
  4. 根據rec構造對應的索引項
  5. 將構造索引項插入sort_buffer塊
  6. 將sort_buffer塊插入新的索引
  7. 處理ddl執行過程中產生的增量(僅rebuild類型需要)

commit階段

  1. 升級到EXCLUSIVE-MDL鎖,禁止讀寫
  2. 重做最后row_log中最后一部分增量
  3. 更新innodb的數據字典表
  4. 提交事務(刷事務的redo日志)
  5. 修改統計信息
  6. rename臨時idb文件,frm文件
  7. 變更完成  

關鍵函數堆棧

拷貝數據

row_merge_build_indexes
     row_merge_read_clustered_index //拷貝全量                                   

   {

       遍歷老表的聚集索引                                            

       row_build //創建一個row

       row_merge_buf_add //將row加入到sort_buffer
       row_merge_insert_index_tuples //插入到新表(聚集索引+二級索引)
   }            
   row_log_table_apply  //對於rebuild類型,處理增量                              

   {

       row_log_table_apply_insert   //以insert為例

       row_log_table_apply_convert_mrec //將buf項轉為tuple

        {

           插入聚集索引 // row_ins_clust_index_entry_low

           插入二級索引 // row_ins_sec_index_entry_low          

        }

   }

對於添加索引的操作,由於不需要修改聚集索引,因此處理增量調用的是另外一套接口

row_log_apply->row_log_apply_ops->row_log_apply_op->row_log_apply_op_low->

修改表數據字典

commit_try_norebuild,commit_try_rebuild

 常見的ddl操作

類型

並發DML

算法

備注

添加/刪除索引

 

Yes

Online(no-rebuild)

全文索引不支持

修改default值

修改列名

修改自增列值

添加/刪除外鍵約束

Yes

Nothing

僅需要修改元數據

添加/刪除列

交換列順序

修改NULL/NOT NULL

修改ROW-FORMAT

添加/修改PK

Optimize table

Yes

 

Online(rebuild)

由於記錄格式改變,需要重建表

修改列類型

刪除PK

轉換字符集

添加全文索引

No

Copy

需要鎖表,不支持online

 若干問題

1.如何實現數據完整性

使用online ddl后,用戶心中一定有一個疑問,一邊做ddl,一邊做dml,表中的數據不會亂嗎?這里面關鍵部件是row_log。row_log記錄了ddl變更過程中新產生的dml操作,並在ddl執行的最后將其應用到新的表中,保證數據完整性。

2.online與數據一致性如何兼得

實際上,online ddl並非整個過程都是online,在prepare階段和commit階段都會持有MDL-Exclusive鎖,禁止讀寫;而在整個ddl執行階段,允許讀寫。由於prepare和commit階段相對於ddl執行階段時間特別短,因此基本可以認為是全程online的。Prepare階段和commit階段的禁止讀寫,主要是為了保證數據一致性。Prepare階段需要生成row_log對象和修改內存的字典;Commit階段,禁止讀寫后,重做最后一部分增量,然后提交,保證數據一致。

3.如何實現server層和innodb層一致性

在prepare階段,server層會生成一個臨時的frm文件,里面包含了新表的格式;innodb層生成了臨時的ibd文件(rebuild方式);在ddl執行階段,將數據從原表拷貝到臨時ibd文件,並且將row_log增量應用到臨時ibd文件;在commit階段,innodb層修改表的數據字典,然后提交;最后innodb層和mysql層面分別重命名frm和idb文件。

 4.對innodb表做ddl過程中異常了,為啥再次做ddl報#sql-xxx already exists

這個錯誤是什么鬼?這個表#sql-xxx實質是做ddl產生的臨時表,ddl異常退出后(比如進程被kill,或者機器異常掉電等),臨時文件沒有清理。再次執行時,會創建同名的#sql-xxx臨時文件,從而導致報錯。這里的xxx與table-id強相關,如果是這樣,我們把這個討厭的#sql-xxx臨時文件刪掉如何呢?再次重做ddl發現還是報同樣的錯誤。這主要原因是,這個臨時表信息在innodb的數據字典有殘留,通過查詢數據字典視圖information_schema.innodb_sys_tables,可以發現存在一條#sql-xxx的表記錄。
深層次原因:ddl整個過程不是原子的,prepare過程中會新建frm文件,ibd文件,並更新數據字典;然后再進行拷貝全量+重放增量操作;最后再rename frm文件,idb文件,並修改數據字典。由於整個過程涉及到server層和innodb層,並不是一個大事務(每次改數據字典都是單獨一個事務),所以執行過程中如果異常終止,就會導致臨時表數據字典殘留在系統表內。

影響:雖然臨時表信息殘留在數據字典內,但不影響用戶后續操作。

解決方法:由於臨時表與table-id強相關,如何改變table-id是我們需要做的,但表又不能被修改,table-id改變不了。這就成了一個悖論,要做ddl,需要改變table-id;要改變table-id,又需要通過ddl操作。查看源碼后發現,對於online ddl,臨時表名依賴於變更表的table-id(比如#sql-ib79,79就是變更表的table-id),而對於copy類型(非online)的ddl,臨時表名則不依賴於table-id(由mysqld進程號+連接會話號產生,比如sql-604d_2,604d是mysqld進程號,2是會話號)。因此,我們通過copy類型的ddl,就可以產生表名不一樣的臨時表了,也就可以完成ddl任務了。比如:alter table test_log add column c88 int, ALGORITHM=copy;

其它:ddl異常結束,會導致重做ddl失敗。如果做ddl過程中,kill query,這個時候ddl也會退出,但退出前會做好善后工作,清理數據字典,因此再次做ddl不會存在問題。

 

參考文檔

http://hedengcheng.com/?p=405

http://hedengcheng.com/?p=421

http://hedengcheng.com/?p=148

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM