1. Online DDL
在 MySQL 5.1 (帶InnoDB Plugin)和5.5中,有個新特性叫 Fast Index Creation(下稱 FIC),就是在添加或者刪除二級索引的時候,可以不用復制原表。對於之前的版本對於索引的添加刪除這類DDL操作,MySQL數據庫的操作過程為如下:
- 首先新建Temp table,表結構是 ALTAR TABLE 新定義的結構
- 然后把原表中數據導入到這個Temp table
- 刪除原表
- 最后把臨時表rename為原來的表名
為了保持數據的一致性,中間復制數據(Copy Table)全程鎖表只讀,如果有寫請求進來將無法提供服務,連接數爆張。
引入FIC之后,創建二級索引時會對原表加上一個S鎖,創建過程不需要重建表(no-rebuild);刪除InnoDB二級索引只需要更新內部視圖,並標記這個索引的空間可用,去掉數據庫元數據上該索引的定義即可。這個過程也只允許讀操作,不能寫入,但大大加快了修改索引的速度(不含主鍵索引,InnoDB IOT的特性決定了修改主鍵依然需要 Copy Table )。
FIC只對索引的創建刪除有效,MySQL 5.6 Online DDL把這種特性擴展到了添加列、刪除列、修改列類型、列重命名、設置默認值等等,實際效果要看所使用的選項和操作類別來定。
1.1 Online DDL選項
MySQL 在線DDL分為 INPLACE
和 COPY
兩種方式,通過在ALTER語句的ALGORITHM參數指定。
ALGORITHM=INPLACE
,可以避免重建表帶來的IO和CPU消耗,保證ddl期間依然有良好的性能和並發。ALGORITHM=COPY
,需要拷貝原始表,所以不允許並發DML寫操作,可讀。這種copy方式的效率還是不如 inplace ,因為前者需要記錄undo和redo log,而且因為臨時占用buffer pool引起短時間內性能受影響。
上面只是 Online DDL 內部的實現方式,此外還有 LOCK 選項控制是否鎖表,根據不同的DDL操作類型有不同的表現:默認mysql盡可能不去鎖表,但是像修改主鍵這樣的昂貴操作不得不選擇鎖表。
LOCK=NONE
,即DDL期間允許並發讀寫涉及的表,比如為了保證 ALTER TABLE 時不影響用戶注冊或支付,可以明確指定,好處是如果不幸該 alter語句不支持對該表的繼續寫入,則會提示失敗,而不會直接發到庫上執行。ALGORITHM=COPY
默認LOCK級別LOCK=SHARED
,即DDL期間表上的寫操作會被阻塞,但不影響讀取。LOCK=DEFAULT
,讓mysql自己去判斷lock的模式,原則是mysql盡可能不去鎖表LOCK=EXCLUSIVE
,即DDL期間該表不可用,堵塞任何讀寫請求。如果你想alter操作在最短的時間內完成,或者表短時間內不可用能接受,可以手動指定。
但是有一點需要說明,無論任何模式下,online ddl開始之前都需要一個短時間排它鎖(exclusive)來准備環境,所以alter命令發出后,會首先等待該表上的其它操作完成,在alter命令之后的請求會出現等待waiting meta data lock
。同樣在ddl結束之前,也要等待alter期間所有的事務完成,也會堵塞一小段時間。所以盡量在ALTER TABLE之前確保沒有大事務在執行,否則一樣出現連環鎖表。
1.2 考慮不同的DDL操作類別
從上面的介紹可以看出,不是5.6支持在線ddl就可以隨心所欲的alter table,鎖不鎖表要看情況:
提示:下表根據官方 Summary of Online Status for DDL Operations 整理挑選的常用操作。
- In-Place 為Yes是優選項,說明該操作支持INPLACE
- Copies Table 為No是優選項,因為為Yes需要重建表。大部分情況與In-Place是相反的
- Allows Concurrent DML? 為Yes是優選項,說明ddl期間表依然可讀寫,可以指定 LOCK=NONE(如果操作允許的話mysql自動就是NONE)
- Allows Concurrent Query? 默認所有DDL操作期間都允許查詢請求,放在這只是便於參考
- Notes 會對前面幾列Yes/No帶 * 號的限制說明
Operation | In-Place? | Copies Table? | Allows Concurrent DML? | Allows Concurrent Query? | Notes |
---|---|---|---|---|---|
添加索引 | Yes* | No* | Yes | Yes | 對全文索引的一些限制 |
刪除索引 | Yes | No | Yes | Yes | 僅修改表的元數據 |
OPTIMIZE TABLE | Yes | Yes | Yes | Yes | 從 5.6.17開始使用ALGORITHM=INPLACE,當然如果指定了old_alter_table=1 或mysqld啟動帶--skip-new 則將還是COPY模式。如果表上有全文索引只支持COPY |
對一列設置默認值 | Yes | No | Yes | Yes | 僅修改表的元數據 |
對一列修改auto-increment 的值 | Yes | No | Yes | Yes | 僅修改表的元數據 |
添加 foreign key constraint | Yes* | No* | Yes | Yes | 為了避免拷貝表,在約束創建時會禁用foreign_key_checks |
刪除 foreign key constraint | Yes | No | Yes | Yes | foreign_key_checks 不影響 |
改變列名 | Yes* | No* | Yes* | Yes | 為了允許DML並發, 如果保持相同數據類型,僅改變列名 |
添加列 | Yes* | Yes* | Yes* | Yes | 盡管允許 ALGORITHM=INPLACE ,但數據大幅重組,所以它仍然是一項昂貴的操作。當添加列是auto-increment,不允許DML並發 |
刪除列 | Yes | Yes* | Yes | Yes | 盡管允許 ALGORITHM=INPLACE ,但數據大幅重組,所以它仍然是一項昂貴的操作 |
修改列數據類型 | No | Yes* | No | Yes | 修改類型或添加長度,都會拷貝表,而且不允許更新操作 |
更改列順序 | Yes | Yes | Yes | Yes | 盡管允許 ALGORITHM=INPLACE ,但數據大幅重組,所以它仍然是一項昂貴的操作 |
修改ROW_FORMAT 和KEY_BLOCK_SIZE |
Yes | Yes | Yes | Yes | 盡管允許 ALGORITHM=INPLACE ,但數據大幅重組,所以它仍然是一項昂貴的操作 |
設置列屬性NULL 或NOT NULL |
Yes | Yes | Yes | Yes | 盡管允許 ALGORITHM=INPLACE ,但數據大幅重組,所以它仍然是一項昂貴的操作 |
添加主鍵 | Yes* | Yes | Yes | Yes | 盡管允許 ALGORITHM=INPLACE ,但數據大幅重組,所以它仍然是一項昂貴的操作。 如果列定義必須轉化NOT NULL,則不允許INPLACE |
刪除並添加主鍵 | Yes | Yes | Yes | Yes | 在同一個 ALTER TABLE 語句刪除就主鍵、添加新主鍵時,才允許inplace;數據大幅重組,所以它仍然是一項昂貴的操作。 |
刪除主鍵 | No | Yes | No | Yes | 不允許並發DML,要拷貝表,而且如果沒有在同一 ATLER TABLE 語句里同時添加主鍵則會收到限制 |
變更表字符集 | No | Yes | No | Yes | 如果新的字符集編碼不同,重建表 |
從表看出,In-Place為No,DML一定是No,說明 ALGORITHM=COPY
一定會發生拷貝表,只讀。但 ALGORITHM=INPLACEE
也要可能發生拷貝表,但可以並發DML:
- 添加、刪除列,改變列順序
- 添加或刪除主鍵
- 改變行格式ROW_FORMAT和壓縮塊大小KEY_BLOCK_SIZE
- 改變列NULL或NOT NULL
- 優化表OPTIMIZE TABLE
- 強制 rebuild 該表
不允許並發DML的情況有:修改列數據類型、刪除主鍵、變更表字符集,即這些類型操作ddl是不能online的。
另外,更改主鍵索引與普通索引處理方式是不一樣的,主鍵即聚集索引,體現了表數據在物理磁盤上的排列,包含了數據行本身,需要拷貝表;而普通索引通過包含主鍵列來定位數據,所以普通索引的創建只需要一次掃描主鍵即可,而且是在已有數據的表上建立二級索引,更緊湊,將來查詢效率更高。
修改主鍵也就意味着要重建所有的普通索引。刪除二級索引更簡單,修改InnoDB系統表信息和數據字典,標記該所以不存在,標記所占用的表空間可以被新索引或數據行重新利用。
1.3 在線DDL的限制
- 在alter table時,如果涉及到table copy操作,要確保
datadir
目錄有足夠的磁盤空間,能夠放的下整張表,因為拷貝表的的操作是直接在數據目錄下進行的。 - 添加索引無需table copy,但要確保
tmpdir
目錄足夠存下索引一列的數據(如果是組合索引,當前臨時排序文件一合並到原表上就會刪除) - 在主從環境下,主庫執行alter命令在完成之前是不會進入binlog記錄事件,如果允許dml操作則不影響記錄時間,所以期間不會導致延遲。然而,由於從庫是單個SQL Thread按順序應用relay log,輪到ALTER語句時直到執行完才能下一條,所以從庫會在master ddl完成后開始產生延遲。(pt-osc可以控制延遲時間,所以這種場景下它更合適)
- During each online DDL ALTER TABLE statement, regardless of the LOCK clause, there are brief periods at the beginning and end requiring an exclusive lock on the table (the same kind of lock specified by the LOCK=EXCLUSIVE clause). Thus, an online DDL operation might wait before starting if there is a long-running transaction performing inserts, updates, deletes, or SELECT … FOR UPDATE on that table; and an online DDL operation might wait before finishing if a similar long-running transaction was started while the ALTER TABLE was in progress.
- 在執行一個允許並發DML在線 ALTER TABLE時,結束之前這個線程會應用 online log 記錄的增量修改,而這些修改是其它thread里產生的,所以有可能會遇到重復鍵值錯誤 (ERROR 1062 (23000): Duplicate entry)。
- 涉及到table copy時,目前還沒有機制限制暫停ddl,或者限制IO閥值
在MySQL 5.7.6開始能夠通過 performance_schema 觀察alter table的進度 - 一般來說,建議把多個alter語句合並在一起進行,避免多次table rebuild帶來的消耗。但是也要注意分組,比如需要copy table和只需inplace就能完成的,應該分兩個alter語句。
- 如果DDL執行時間很長,期間又產生了大量的dml操作,以至於超過了
innodb_online_alter_log_max_size
變量所指定的大小,會引起 DB_ONLINE_LOG_TOO_BIG 錯誤。默認為 128M,特別對於需要拷貝大表的alter操作,考慮臨時加大該值,以此獲得更大的日志緩存空間 - 執行完
ALTER TABLE
之后,最好ANALYZE TABLE tb1
去更新索引統計信息
2. 實現過程
online ddl主要包括3個階段,prepare階段,ddl執行階段,commit階段,rebuild方式比no-rebuild方式實質多了一個ddl執行階段,prepare階段和commit階段類似。下面將主要介紹ddl執行過程中三個階段的流程。
-
Prepare階段 :
- 創建新的臨時frm文件(與InnoDB無關)
- 持有EXCLUSIVE-MDL鎖,禁止讀寫
- 根據alter類型,確定執行方式(copy,online-rebuild,online-norebuild)
假如是Add Index,則選擇online-norebuild即INPLACE方式 - 更新數據字典的內存對象
- 分配row_log對象記錄增量(僅rebuild類型需要)
- 生成新的臨時ibd文件(僅rebuild類型需要)
-
ddl執行階段 :
- 降級EXCLUSIVE-MDL鎖,允許讀寫
- 掃描old_table的聚集索引每一條記錄rec
- 遍歷新表的聚集索引和二級索引,逐一處理
- 根據rec構造對應的索引項
- 將構造索引項插入sort_buffer塊排序
- 將sort_buffer塊更新到新的索引上
- 記錄ddl執行過程中產生的增量(僅rebuild類型需要)
- 重放row_log中的操作到新索引上(no-rebuild數據是在原表上更新的)
- 重放row_log間產生dml操作append到row_log最后一個Block
-
commit階段 :
- 當前Block為row_log最后一個時,禁止讀寫,升級到EXCLUSIVE-MDL鎖
- 重做row_log中最后一部分增量
- 更新innodb的數據字典表
- 提交事務(刷事務的redo日志)
- 修改統計信息
- rename臨時idb文件,frm文件
- 變更完成
3. Online DDL 的思維導向圖
4. Online DDL 注意事項:
- 磁盤空間
- rebuild 的時候,datadir空間是否足夠
- 因為會拷貝ibd文件,所以要確保空間足夠
- rebuild 的時候,innodb_online_alter_log_max_size是否足夠
- rebuild過程中,產生的DML涉及到行記錄變更日志,是否足夠存儲
- inplace的時候,考慮tmpdir空間是否足夠
- rebuild 的時候,datadir空間是否足夠
- ddl對從庫延遲的影響是否可以接受
- 主庫online DDL的過程中,由於沒有commit,所以其他並發操作可以正常同步到從庫
- 主庫commit后,DDL同步到從庫
- 由於從庫是單線程執行SQL_THREAD,假設DDL執行過程需要1個小時,那么從庫將會滯后1小時+
- 是否允許從庫的滯后,如果不允許,可以通過並行復制來優化處理
- row-log會檢查重復值或者修改沖突嗎?
- 會根據主鍵及唯一約束來檢查
- copy table ,inplace下如何暫停DDL操作
- show full processlist;
- kill id; #( DDL SQL的id號)
- 這里kill完后,仍然可以再次正常執行DDL,不會存在沖突,其創建的臨時idb及frm文件會自動刪除
- copy table ,inplace下宕機
- 這兩種情況下宕機后,沒有完成的DDL語句不會繼續執行
- 但是,其生成的frm跟idb臨時文件不會被刪除,可以手動刪除,也可以不手動刪除,即使不刪除,也不會影響再次執行DDL
- 但建議mysql服務后,刪除無用的臨時文件
- 同個表格多個DDL語句,不要一個個執行
- 請按照是否支持inplace及是否需要rebuild分類合並執行
5. 5.7 的版本Online DDL 可以通過P_S 查看進度
一、打開功能
mysql> UPDATE setup_instruments SET ENABLED = 'YES' WHERE NAME LIKE 'stage/innodb/alter%'; Query OK, 7 rows affected (0.00 sec) Rows matched: 7 Changed: 7 Warnings: 0 mysql> UPDATE setup_consumers SET ENABLED = 'YES' WHERE NAME LIKE '%stages%'; Query OK, 3 rows affected (0.00 sec) Rows matched: 3 Changed: 3 Warnings: 0
二、執行改表操作
mysql> ALTER TABLE employees.employees ADD COLUMN middle_name varchar(14) AFTER first_name; Query OK, 0 rows affected (9.27 sec) Records: 0 Duplicates: 0 Warnings: 0
三、查看改表進度
mysql> SELECT EVENT_NAME, WORK_COMPLETED, WORK_ESTIMATED FROM events_stages_current; +------------------------------------------------------+----------------+----------------+ | EVENT_NAME | WORK_COMPLETED | WORK_ESTIMATED | +------------------------------------------------------+----------------+----------------+ | stage/innodb/alter table (read PK and internal sort) | 280 | 1245 | +------------------------------------------------------+----------------+----------------+ 1 row in set (0.01 sec)