MySQL 5.7 特性:Online DDL
MySQL DDL 的方法
MySQL 的 DDL 有很多種方法。
MySQL 本身自帶三種方法,分別是:copy、inplace、instant。
- copy 演算法為最古老的演算法,在 MySQL 5.5 及以下為預設演算法。
- 從 MySQL 5.6 開始,引入了 inplace 演算法並且預設使用。inplace 演算法還包含兩種型別:rebuild-table 和 not-rebuild-table。MySQL 使用 inplace 演算法時,會自動判斷,能使用 not-rebuild-table 的情況下會盡量使用,不能的時候才會使用 rebuild-table。當 DDL 涉及到主鍵和全文索引相關的操作時,無法使用 not-rebuild-table,必須使用 rebuild-table。其他情況下都會使用 not-rebuild-table。
- 從 MySQL 8.0.12 開始,引入了 instant 演算法並且預設使用。目前 instant 演算法只支援增加列等少量 DDL 型別的操作,其他型別仍然會預設使用 inplace。
有一些第三方工具也可以實現 DDL 操作,最常見的是 percona 的 pt-online-schema-change 工具(簡稱為 pt-osc),和 github 的 gh-ost 工具,均支援 MySQL 5.5 以上的版本。
各類工具的對比
方法 |
copy |
inplace not-rebuild-table |
inplace rebuild-table |
instant |
pt-osc |
gh-ost |
---|---|---|---|---|---|---|
DDL 過程中讀取資料 |
允許 |
允許 |
允許 |
允許 |
允許 |
允許 |
DDL 過程中寫入資料 |
不允許 |
允許 |
允許 |
允許 |
允許 |
允許 |
需要 MDL |
需要 |
需要 |
需要 |
需要 |
需要 |
需要 |
需要額外空間 |
大 |
小 |
中 |
小 |
大 |
大 |
執行時間 |
非常長 |
短 |
非常長 |
非常短 |
長 |
長 |
IO 負載 |
大 |
小 |
大 |
非常小 |
非常大 |
大 |
導致主從同步延時 |
非常大 |
大 |
大 |
非常小 |
小 |
小 |
其他 |
支援臨時暫停 |
一般情況下的建議:
- 如果使用的是 MySQL 5.5 或者 MySQL 5.6,推薦使用 gh-ost
- 如果使用的是 MySQL 5.7,索引等不涉及修改資料的操作,建議使用預設的 inplace 演算法。如果涉及到修改資料(例如增加列),不關心主從同步延時的情況下使用預設的 inplace 演算法,關心主從同步延時的情況下使用 gh-ost
- 如果使用的是 MySQL 8.0,推薦使用 MySQL 預設的演算法設定,在語句不支援 instant 演算法並且在意主從同步延時的情況下使用 gh-ost
各類工具的使用方法
copy
MySQL 5.5 及以下,直接正常 DDL 即可。
MySQL 5.6 及以上,如果希望使用 copy 演算法,需要使用 ALGORITHM=COPY
指定演算法型別,例如:
ALTER TABLE table1 ADD COLUMN column1 int ALGORITHM=COPY;
inplace
MySQL 5.7,直接正常 DDL 即可。
MySQL 8.0 及以上,如果希望使用 inplace 演算法,需要使用 ALGORITHM=INPLACE
指定演算法型別。
instant
MySQL 8.0 ,直接正常 DDL 即可。
pt-online-schema-change
比 gh-ost 落後很多,不推薦使用此工具。
gh-ost
參考其他的
MySQL DDL 的使用注意事項
MySQL 在大型表上的 DDL 會帶來耗時較久、負載較高、額外空間佔用、MDL、主從同步延時等情況。需要特別引起重視。
大部分情況在上面的效能對比表格已經描述,這裡不再重複,這裡著重講一些重點問題:
DDL 的所需時間
DDL 的執行時間,和很多因素相關,如果需要比較精確的時間預估,建議在測試環境提前做測試。
可以新建一個測試例項,將備份資料匯出到測試例項,執行 DDL 操作,判斷執行時間,作為對線上執行的一個估計。但是請注意,該估計仍然可能不準確,因為線上例項的負載可能會比測試例項高。
如果使用的是 gh-ost,工具會反饋執行進度。如果使用的是 MySQL 自帶的 DDL,MySQL 5.7 可以開啟 DDL 監控,使用以下語句檢視 DDL 執行進度:
SELECT EVENT_NAME, WORK_COMPLETED, WORK_ESTIMATED FROM performance_schema.events_stages_current;
MySQL 自帶的監控也是估算值,因此進可作參考。
負載
所有方式對大表做 DDL 都會增加負載,只是程度的不同,主要為 IO 的負載。如果是 IO 使用非常高的例項,建議在 IO 較小的時間段執行 DDL 操作。
額外空間佔用
copy、inplace rebuild-table、gh-ost、pt-online-schema-change,都會將表完整複製一份出來再做 DDL 變更,因此會使用和原表空間一樣大(甚至更大,如果是加列的操作的話)的額外空間,另外還會生成大量的臨時日誌。要特別注意剩餘空間,確保空間充裕,不然可能導致 DDL 過程中磁碟寫滿。
主從同步延時
所有方式做 DDL 均會引發主從同步延時。其中 copy 和 inplace 演算法,只有主完成了 DDL 操作之後,binlog 才會同步給從庫,從庫才能開始操作 DDL,從庫操作完 DDL 之後才能開始操作其他語句,因此會造成巨大的(大概兩倍 DDL 操作時間)的延時。其他方法產生的延時較小,但仍然可能有幾秒的延時。
MDL
所有方式做 DDL 均會產生 MDL(metadata lock)。除了 copy 模式會有持續性的鎖(DDL 的整個過程期間無法向該表寫入任何資料)之外,其他方式的 MDL 均為短暫的鎖。
除了 copy 模式之外的所有模式,MDL 如下:
- 在 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 語句
其他
MySQL 的 inplace 演算法雖然支援在 DDL 過程中間的讀寫,但是對寫入的資料量有上限,不能超過 innodb_online_alter_log_max_size
(預設為 128M)。如果超過上限可能導致執行失敗。
MySQL DDL 的原理簡析
copy 演算法
較簡單的實現方法,MySQL 會建立一個新的臨時表,把源表的所有資料寫入到臨時表,在此期間無法對源表進行資料寫入。MySQL 在完成臨時表的寫入之後,用臨時表替換掉源表。這個演算法主要被早期(<=5.5)版本所使用。
inplace 演算法
從 5.6 開始,常用的 DDL 都預設使用這個演算法。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 鎖。
instant 演算法
MySQL 8.0.12 才提出的新演算法,目前只支援新增列等少量操作,利用 8.0 新的表結構設計,可以直接修改表的 metadata 資料,省掉了 rebuild 的過程,極大的縮短了 DDL 語句的執行時間。
pt-online-schema-change
借鑑了 copy 演算法的思路,由外部工具來完成臨時表的建立,資料同步,用臨時表替換源表這三個步驟。其中資料同步是利用 MySQL 的觸發器來實現的,會少量影響到線上業務的 QPS 及 SQL 響應時間。