背景
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方式,比如修改列類型,刪除主鍵等;對於inplace方式,mysql內部以“是否修改記錄格式”為基准也分為兩類,一類需要重建表(修改記錄格式),比如添加、刪除列、修改列默認值等;另外一類是只需要修改表的元數據,比如添加、刪除索引、修改列名等。Mysql將這兩類方式分別稱為rebuild方式和no-rebuild方式。online ddl主要包括3個階段,prepare階段,ddl執行階段,commit階段,rebuild方式比no-rebuild方式實質多了一個ddl執行階段,prepare階段和commit階段類似。下面將主要介紹ddl執行過程中三個階段的流程。
Prepare階段:
ddl執行階段:
commit階段
關鍵函數堆棧
拷貝數據
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
}
}
修改表數據字典
commit_try_norebuild,commit_try_rebuild
常見的ddl操作
類型
並發DML
算法
備注
添加/刪除索引
Yes
Online(no-rebuild)
全文索引不支持
修改default值
修改列名
修改自增列值
Yes
Nothing
僅需要修改元數據
添加/刪除列
交換列順序
修改NULL/NOT NULL
修改ROW-FORMAT
添加/修改主鍵
Yes
Online(rebuild)
由於記錄格式改變,需要重建表
修改列類型
Optimize table
轉換字符集
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文件。
參考文檔
http://hedengcheng.com/?p=405
http://hedengcheng.com/?p=421
http://hedengcheng.com/?p=148