add by zhj: 關系型數據庫有Schema,修改Schema的時間比較長,一般都要秒級和分鍾級。主備延遲有相同的數量級,對於不支持Inplace方式的DDL,只能用Copy方式,Copy方式在執行DDL時,整個表不可讀寫,
影響非常大。不過,對於MySQL5.6(尤其是MySQL5.7),絕大部分常用的DDL都支持Inplace,DDL加MDL寫鎖的時間很短,所以主要評估主備延遲的影響就可以了。另外,不要使用大事務,因為事務中主要就是DML語句,
而DML語句會加MDL讀鎖,這會導致DDL在加MDL寫鎖時等待,而這個等待期間,會其它事務無法獲取MDL讀鎖,即該表不可讀,不可寫,其它事務在等待超時后,會回滾,這個影響比較嚴重的。執行DDL最好在流量小的
時候,以盡量降低影響,一般是凌晨。 文中提到了在不同情況下使用的工具。
原文:https://dbaplus.cn/news-11-2552-1.html
王文安,網易游戲MySQL DBA, 主要負責網易游戲MySQL SaaS平台的設計與維護,也有關注TiDB、CockRoachDB等分布式數據庫。更多文章發布在網易游戲運維平台訂閱號(ID:neteasegameops)
DDL 一向是業務的痛點,尤其是對大型表的 DDL 操作,具有操作時間久,對性能影響大,可能影響業務正常使用等問題。
本文將詳細解釋 MySQL DDL 的原理,並分享一些盡可能減少 DDL 對業務的影響的辦法。
一、MySQL DDL的方法
MySQL 的 DDL 有很多種算法。
MySQL 本身自帶三種算法:
-
COPY 算法,為最古老的算法,在 MySQL 5.5 及以下為默認算法。
-
INPLACE 算法,從 MySQL 5.6 開始被引入並默認使用。
INPLACE 算法還包含兩種類型:rebuild-table 和 not-rebuild-table。MySQL 使用 INPLACE 算法時,會自動判斷,能使用 not-rebuild-table 的情況下會盡量使用,不能的時候才會使用 rebuild-table。當 DDL 涉及到主鍵和全文索引相關的操作時,無法使用 not-rebuild-table,必須使用 rebuild-table。其他情況下都會使用 not-rebuild-table。
-
INSTANT 算法,從 MySQL 8.0.12 開始被引入並默認使用。目前 INSTANT 算法只支持增加列等少量 DDL 類型的操作,其他類型仍然會默認使用 INPLACE。
有一些第三方工具也可以實現 DDL 操作,最常見的是 percona 的 pt-online-schema-change 工具(簡稱為 pt-osc),和 GitHub 的 gh-ost 工具,均支持 MySQL 5.5 以上的版本。
1、各類工具的對比
推薦工具:
2、各類工具的使用方法
1)COPY
MySQL 5.5 及以下,直接正常 DDL 即可。
MySQL 5.6 及以上,如果希望使用 COPY 算法,需要使用 ALGORITHM=COPY 指定算法類型,例如:
ALTER TABLE table1 ADD COLUMN column1 INT ALGORITHM=COPY;
2)INPLACE
MySQL 5.7,直接正常 DDL 即可。
MySQL 8.0 及以上,如果希望使用 INPLACE 算法,需要使用 ALGORITHM=INPLACE 指定算法類型。
3)INSTANT
MySQL 8.0 ,直接正常 DDL 即可。目前已經有以下 DDL 類型支持 INSTANT:
-
添加列。
-
添加或者刪除一個虛擬列(不支持刪除普通列)。
-
添加或者刪除一個列的默認值。
-
修改 ENUM 或者 SET 列的定義。
-
變更索引的類型(B樹,哈希)。
-
使用 ALTER 語法重命名表。
可以使用 ALGORITHM = INSTANT 強制使用 INSTANT 算法。如果不支持則 MySQL 會報錯,不進行任何操作。
需要注意以下幾點:
-
添加列時,INSTANCE 算法不能使用 AFTER 關鍵字控制列的位置,只能添加在表的末尾(最后一列)。
-
開啟壓縮的 InnoDB 表無法使用 INSTANT 算法。
-
不支持包含全文索引的表。
-
僅支持使用 MySQL 8.0 新表空間格式的表。
-
不支持臨時表。
-
包含 INSTANT 列的表無法在舊版本的 MySQL上使用(即物理備份無法恢復)。
-
在舊版本上,如果表或者表的索引已經損壞,除非已經執行 fix 或者 rebuild,否則升級到新版本后無法添加 INSTANT 列。
pt-online-schema-change
參見工具官方文檔:
https://www.percona.com/doc/percona-toolkit/LATEST/pt-online-schema-change.html
gh-ost
參見工具官方文檔:
https://github.com/github/gh-ost
二、MySQL DDL使用注意事項
MySQL 在大型表上的 DDL 會帶來耗時較久、負載較高、額外空間占用、MDL、主從同步延時等情況。需要特別引起重視。
大部分情況在上面的性能對比表格已經描述,這里不再重復,這里着重講一些重點問題:
1、DDL的所需時間
DDL 的執行時間,和非常非常多的因素相關,很難預先對執行時間做出估計。如果項目非常在意這個時間,建議提前做測試。
可以新建一個新的測試實例,將備份數據導出到測試實例,執行 DDL 操作,判斷執行時間,作為對線上執行的一個估計。但是請注意,該估計仍然可能不准確,因為線上實例的負載可能會比測試實例高。
如果使用的是 gh-ost,工具會反饋執行進度。
如果使用的是 MySQL 自帶的 DDL,可以開啟 InnoDB 的 DDL 監控( MySQL 默認不開啟),開啟方法為在 my.cnf 中增加配置:
performance-schema-instrument = stage/innodb/alter%=ON
開啟之后可以使用以下語句查看 DDL 執行進度:
SELECT EVENT_NAME, WORK_COMPLETED, WORK_ESTIMATED FROM performance_schema.events_stages_current;
2、負載
所有方式對大表做 DDL 都會增加負載,只是程度的不同,主要為 IO 的負載。如果是 IO 使用非常高的實例,建議在 IO 較小的時間段執行 DDL 操作。
3、額外空間占用
COPY、INPLACE rebuild-table、gh-ost、pt-online-schema-change,都會將表完整復制一份出來再做 DDL 變更,因此會使用和原表空間一樣大(甚至更大,如果是加列的操作的話)的額外空間,另外還會生成大量的臨時日志。
請特別注意剩余空間,確保空間充裕,不然可能導致 DDL 過程中 MySQL 崩潰。
4、主從同步延時
所有方式做 DDL 均會引發主從同步延時。
其中 COPY 和 INPLACE 算法,只有主完成了 DDL 操作之后,binlog 才會同步給從,從才能開始操作 DDL,從操作完 DDL 之后才能開始操作其他語句,從而會造成巨大的(大概兩倍 DDL 操作時間)的延時。
其他方法產生的延時較小,但仍然可能有幾秒的延時。
5、MDL
除了 COPY 算法會有持續性的鎖(DDL 的整個過程期間無法向該表寫入任何數據)之外,所有方式做 DDL 均只會產生短暫的 MDL(metadata lock)。
除了 COPY 模式之外的所有模式,大概的鎖過程如下:
-
在 DDL 的開始階段,申請該表的 EXCLUSIVE-MDL 鎖,禁止讀寫
-
降級 EXCLUSIVE-MDL 鎖,允許讀寫
-
在 DDL 的最終 COMMIT 階段,升級 EXCLUSIVE-MDL 鎖,禁止讀寫
其中的階段一和階段三,其 MDL 的持續時間都是非常短暫的,也就是申請到了 MDL 鎖之后會在很快的時間(一般小於一秒)處理完成相關操作並釋放鎖,一般情況下是不會影響業務的。
只有階段二是真正在處理數據,持續時間較長,但是階段二期間是允許數據讀寫的。
還有可能出現在階段一和階段三無法申請到 MDL 的情況。這是因為 MDL 和所有的讀寫語句都是沖突的。
如果是在申請 MDL 的時候,之前有讀寫的事務一直沒有執行完成(或者執行完成之后一直沒有 COMMIT),MDL 就會無法立刻申請到,這個時候,DDL 語句以及所有在該 DDL 語句之后的讀寫事務,都會阻塞並等待之前的讀寫事務完成,導致整個實例處於不可用狀態。
這個時候 SHOW PROCESSLIST 看到的語句狀態為 waiting for metadata lock。
由於目前所有的 DDL 語句都會產生 MDL,無法避免,因此在執行 DDL 操作期間,請盡可能確保不要有未執行完成的長事務。
如果發生了 warting for metadata lock 導致的阻塞,有以下三種處理方法:
-
耐心等待之前的事務全部執行完成。
-
將之前未執行完成的事務全部 kill 掉。
-
kill 掉 DDL 語句。
如果想要知道當前 MDL 的情況,可以開啟 MDL 監控(MySQL 8.0 以上默認開啟),開啟方法為在 my.cnf 中增加配置:
performance-schema-instrument = wait/lock/metadata/sql/mdl=ON
開啟后,可以使用以下語句來查看 MDL 的當前情況,判斷是哪些事務導致的阻塞,從而可以 kill 掉這些事務:
SELECT OBJECT_TYPE,OBJECT_SCHEMA,OBJECT_NAME,LOCK_STATUS,processlist_id
FROM performance_schema.metadata_locks mdl
INNER JOIN performance_schema.threads thd ON mdl.owner_thread_id = thd.thread_id
WHERE processlist_id <> @@pseudo_thread_id;
6、其他
MySQL 的 INPLACE 算法雖然支持在 DDL 過程中間的讀寫,但是對寫入的數據量有上限,不能超過 innodb_online_alter_log_max_size(默認為 128M)。
如果超過上限可能導致執行失敗。如果有超過該上限的可能,請調大該參數。
三、MySQL DDL原理簡析
以 MySQL 5.7 為准,目前各類 DDL 操作所使用的算法見下圖:
1、COPY算法
較簡單的實現方法,MySQL 會建立一個新的臨時表,把源表的所有數據寫入到臨時表,在此期間無法對源表進行數據寫入。MySQL 在完成臨時表的寫入之后,用臨時表替換掉源表。
2、INPLACE算法
INPLACE 算法包含兩類:inplace-no-rebuild 和 inplace-rebuild,兩者的主要差異在於是否需要重建源表。
INPLACE 算法的操作階段主要分為三個:
Prepare階段:
-
創建新的臨時 frm 文件(與 InnoDB 無關)。
-
持有 EXCLUSIVE-MDL 鎖,禁止讀寫。
-
根據 ALTER 類型,確定執行方式(copy,online-rebuild,online-not-rebuild)。
-
更新數據字典的內存對象。
-
分配 row_log 對象記錄數據變更的增量(僅 rebuild 類型需要)。
-
生成新的臨時ibd文件 new_table(僅 rebuild 類型需要)。
Execute 階段:
-
降級 EXCLUSIVE-MDL 鎖,允許讀寫。
-
掃描 old_table 聚集索引(主鍵)中的每一條記錄(稱為 rec)。
-
遍歷 new_table 的聚集索引和二級索引,逐一處理。
-
根據 rec 構造對應的索引項。
-
將構造索引項插入 sort_buffer 塊排序。
-
將 sort_buffer 塊更新到 new_table 的索引上。
-
記錄 online-ddl 執行過程中產生的增量(僅 rebuild 類型需要)。
-
重放 row_log 中的操作到 new_table 的索引上(not-rebuild 數據是在原表上更新)。
-
重放 row_log 中的 DML 操作到 new_table 的數據行上。
Commit 階段:
-
當前 Block 為 row_log 最后一個時,禁止讀寫,升級到 EXCLUSIVE-MDL 鎖。
-
重做 row_log 中最后一部分增量。
-
更新 innodb 的數據字典表。
-
提交事務(刷事務的 redo 日志)。
-
修改統計信息。
-
rename 臨時 ibd 文件,frm 文件。
-
變更完成,釋放 EXCLUSIVE-MDL 鎖。
3、INSTANT算法
MySQL 8.0.12 才提出的新算法,目前只支持添加列等少量操作,利用 8.0 新的表結構設計,可以直接修改表的 metadata 數據,省掉了 rebuild 的過程,極大的縮短了 DDL 語句的執行時間。
新的算法依賴於 MySQL 8.0 對表 metadata 結構做出的一些變更。
8.0 除了在表的 metadata 信息中新增了 INSTANT 列的默認值以及非 INSTANT 列的數量以外,還在數據的物理記錄中加入了 info_bit,包括一個 flag 來標記這條記錄是否為添加 INSTANT 列之后才更新、插入的,以及 column_num,用來記錄行數據總共有多少列。
當使用 INSTANT 算法來添加列的時候,無需 rebuild 表,直接把列的信息記錄到 metadata 中即可,對這些行進行操作時,可以讀取 metadata 的信息來組合出完整的行數據。
各類語句的實現方式也發生了一些變更:
-
SELECT:讀取一行數據的物理記錄時,會根據 flag 來判斷是否需要去 metadata 中獲取 INSTANT 列的信息;如果需要,則根據 column_num 來讀取實際的物理數據,再從 metadata 中補全缺少的 INSTANT 列數據。
-
INSERT:額外記錄語句執行時的 flag 和 column_num。
-
DELETE:與以前的版本保持一致。
-
UPDATE:如果表的 instant column 數量發生了變化,對舊數據的 UPDATE 會在內部轉換成 DELETE 和 INSERT 操作。
當對包含 INSTANT 列的表進行 rebuild 時,所有的數據在 rebuild 的過程中重新以舊的數據格式(包含所有列的內容)寫入到表中,所以 rebuild 表之后,information_schema 中有關這個表的 INSTANT 的信息會被重置。
可以使用如下 SQL 命令可以查看每個表當前 INSTANT 列的數量,如果該表沒有添加過 INSTANT 列,則 instant_cols 默認顯示 0。如果增加過,則最后的該對應數字個數的列為 INSTANT 列(因為 INSTANT 列只能在最末尾)。
SELECT * FROM information_schema.innodb_tables
4、pt-online-schema-change
借鑒了 COPY 算法的思路,由外部工具來完成臨時表的建立,數據同步,用臨時表替換源表這三個步驟。其中數據同步是利用 MySQL 的觸發器來實現的,會少量影響到線上業務的 QPS 及 SQL 響應時間。
由於 pt-osc 使用了 MySQL 的觸發器,在寫入量較大的情況下,觸發器可能導致 MySQL 的負載增大。
5、gh-ost
不使用觸發器,而是直接通過 binlog 來讀取數據並進行同步。避免了 pt-osc 的觸發器導致的負載增大問題。