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 響應時間。