MySQL InnoDB 修改表列Online DDL


概述

一般來說數據庫結構一經設計,不能輕易更改,因為更改DDL(Data Definition Language)操作代價很高,所以在進行數據庫結構設計時需要謹慎。

但是業務發展是未知的,特別是那些變化很大的業務,所以不可避免的需要修改數據庫結構,本文主要對MySQL5.6+ InnoDB存儲引擎字段的修改進行探討。

對於不同的場景,所使用的方式也會大不相同,尤其是修改百萬級,千萬級的表字段時,要特別注意。

DDL操作類型

數據庫結構的DDL操作總體來說有如下幾種:

  • 索引操作(Index Operations)
  • 鍵操作(Primary Key Operations)
  • 列操作(Column Operations)
  • 外鍵操作(Foreign Key Operations)
  • 表操作(Table Operations)
  • 分區操作(Partitioning Operations)

本文主要對列操作(Column Operations)進行探討,其他更詳細的信息參考MySQL官方英文文檔

Online DDL操作

簡述

本文探討的是Online DDL操作,MySQL5.6以上支持,相較於一般DDL,它在實現修改表結構的同時,依然允許DML操作(SELECT,INSERT,UPDATE,DELETE)。

Online DDL主要有兩種方式:IN PLACECOPY

  • IN PLACE:直接在原表上進行修改,相比於COPY方式可以避免重建表帶來的IO和CPU消耗,有更好的性能並支持並發DML操作
  • COPY:創建修改后的臨時表,然后將原表的數據復制到臨時表,執行期間不允許並發DML寫操作,否則會導致臟數據。

在MySQL之前,我們一般使用COPY的方式,借助臨時表,手動修改。

需要注意的是:並不是所有的Online DDL操作都支持IN PLACE方式。

MySQL InnoDB數據存儲方式

在MySQL中,一張表的數據分為兩種,一種是結構數據,記錄者站表包含哪些字段,哪些數據類型,另一種是記錄數據,保存每天記錄的原始數據。它們是用不同的文件進行存儲的。

在mysql指定的data_dir數據存儲目錄可以看到每張表對應一個frm文件,這個文件就是存放着表的結構數據。

INPLACE方式詳細介紹

對於添加索引,添加/刪除列、修改列NULL/NOT NULL屬性等操作,需要修改MySQL內部的數據記錄,對這類操作進行Online DDL操作時,需要重建表(rebuild)。

相反,對於刪除索引,修改列默認值,修改列名等操作不需要修改MySQL內部的數據記錄,只需要修改結構數據frm文件,而不需要重建表(no-rebuild)。

另外,在進行Online DDL操作期間,不同的操作可以選擇不同的鎖機制。主要有以下幾種鎖機制:

  • LOCK=DEFAULT:默認方式,MySQL自行判斷使用哪種LOCK模式,盡量不鎖表
  • LOCK=NONE:無鎖:允許Online DDL期間進行並發讀寫操作。如果Online DDL操作不支持對表的繼續寫入,則DDL操作失敗,對表修改無效
  • LOCK=SHARED:共享鎖:Online DDL操作期間堵塞寫入,不影響讀取
  • LOCK=EXCLUSIVE:排它鎖:Online DDL操作期間不允許對鎖表進行任何操作

無論任何模式下,Online DDL操作開始都需要一小段時間的排它鎖來准備環境,用於等待該表上的其他操作執行完畢,此時Online DDL操作會提示:waiting meta data lock。

同樣在Online DDL操作結束之前,也會等待Online DDL操作期間的事務完成,此時也會出現排它鎖。

所以需要確保在執行Online DDL之前和執行期間沒有大型DML事務占用該表,否則會出現長時間鎖表甚至死鎖。

Online DDL各種列操作情況

從上面的介紹可以看出,不同的DDL操作,執行的具體細節大不相同,詳見下表:

Operation In Place Rebuilds Table Permits Concurrent DML Only Modifies Metadata
Adding a column Yes Yes Yes* No
Dropping a column Yes Yes Yes No
Renaming a column Yes No Yes* Yes
Reordering columns Yes Yes Yes No
Setting a column default value Yes No Yes Yes
Changing the column data type No Yes No No
Extending VARCHAR column size Yes No Yes Yes
Dropping the column default value Yes No Yes Yes
Changing the auto-increment value Yes No Yes No*
Making a column NULL Yes Yes* Yes No
Making a column NOT NULL Yes* Yes* Yes No
Modifying the definition of an ENUM or SET column Yes No Yes Yes

其中各列指標解釋如下:

  • In Place:是否支持In Place方式,Yes為優選方案
  • Re Builds Table:是否需要重建表,不重建(No)為優選方案
  • Permits Concurrent DML:是否允許並發DML操作,允許(Yes)為優選方案
  • Only Modifies Metadata:是否值修改表結構數據,即只修改frm文件

列操作方式

下面列舉常用的列操作的執行方法以及注意事項。

添加列(Adding a column)

為表添加一列的方法如下:

ALTER TABLE tbl_name 
ADD COLUMN column_name column_definition, ALGORITHM=INPLACE, LOCK=NONE;

添加列時如果附加auto increment選項,則不允許並發DML操作,此操作會重建表,開銷巨大。最優化選項是指定:ALGORITHM=INPLACE, LOCK=SHARED

刪除列(Dropping a column)

ALTER TABLE tbl_name 
DROP COLUMN column_name, ALGORITHM=INPLACE, LOCK=NONE;

重命名列名(Renaming a column)

ALTER TABLE tbl 
CHANGE old_col_name new_col_name data_type, ALGORITHM=INPLACE, LOCK=NONE;

如果你的目的只是修改列名,一定要保證修改后的列的數據類型,NULL/NOT NULL等屬性和原來的列一致。

該操作建議指定INPLACE方式,這樣只會更新frm文件,即使修改的列名是外鍵。

重新排列列順序(Reordering columns)

ALTER TABLE tbl_name 
MODIFY COLUMN col_name column_definition FIRST, ALGORITHM=INPLACE, LOCK=NONE;

該操作費力不討好,不建議對數據量超過百萬級的大表進行操作,它會對表重建。

修改列數據類型(Changing the column data type)

ALTER TABLE tbl_name 
CHANGE c1 c1 BIGINT, ALGORITHM=COPY;

修改數據類型只支持COPY方式。

修改列的默認值(Setting a column default value)

ALTER TABLE tbl 
ALTER COLUMN col DROP DEFAULT, ALGORITHM=INPLACE, LOCK=NONE;

修改列的自增熟悉(Changing the auto-increment value)

ALTER TABLE table 
AUTO_INCREMENT=next_value, ALGORITHM=INPLACE, LOCK=NONE;

該操作用於修改下一條記錄的自增值,只會修改內存中的值,而不會修改數據文件。

對於分布式系統,經常需要手動制定開始自增的值,可以使用該方法。

修改NULL/NOT NULL屬性(Making a column NULL and Making a column NOT NULL)

-- Making a column NULL
ALTER TABLE tbl_name 
MODIFY COLUMN column_name data_type NULL, ALGORITHM=INPLACE, LOCK=NONE;

-- Making a column NOT NULL
ALTER TABLE tbl_name 
MODIFY COLUMN column_name data_type NOT NULL, ALGORITHM=INPLACE, LOCK=NONE;

因為設置列為NULL時,該列在原有數據類型空間的基礎上增加一個直接來存儲是否為NULL,所以需要重建表。

當把NULL的列設為NOT NULL時,如果有記錄為NULL,則該操作會失敗。

修改ENUM或SET的定義(Modifying the definition of an ENUM or SET column)

CREATE TABLE t1 (c1 ENUM('a', 'b', 'c'));
ALTER TABLE t1 MODIFY COLUMN c1 ENUM('a', 'b', 'c', 'd'), ALGORITHM=INPLACE, LOCK=NONE;

該方式用於修改一個枚舉或者集合的值,對於在尾部增加枚舉或者集合值的情況,如果增加之后存儲空間沒有變化,就可以使用IN PLACE方式。

反之如果存儲空間發生變化,如從2個字節便到三個字節,或者在中間添加值,那么就需要COPY的方式。

對於那種值的個數不確定或者枚舉名稱變化的場景,建議使用tinyint代替ENUM或者SET來進行存儲。

實際中如何執行DDL修改

綜合上述,可以得出常用的三種方法。

Online DDL

通過執行ALTER等命令直接修改。適用的情況如下:

  • 表中數據量較小,低於百萬級別
  • 需要MySQL5.6+以上
  • 能夠忍受長時間不提供服務的百萬級表,需要一小時以內

手動修改frm文件

該方式適用於不支持Online DDL的場景,只能執行Only Modifies Metadata部分的DDL修改。修改方法如下:

首先找到MySQL數據存儲路徑,可從進程信息中查看:

# 查找mysql進程信息
ps aux|grep mysql

查到當前數據庫的數據存儲目錄,然后cd到所看到的frm表結構文件目錄,備份需要處理的frm文件。

在數據庫創建一個類似的數據表,然后修改該表,再把該表的frm文件和原來的表的frm文件替換。

-- mysql中創建臨時表
create table tbl_temp like tbl;
 
-- 修改臨時表
ALTER TABLE tbl
ADD COLUMN `count`  bigint(20) NOT NULL DEFAULT 0 COMMENT '';
-- 鎖表
flush tables with write lock;
 
-- 備份源文件
cp tbl.frm tbl.frm.bak
# 替換數據結構文件frm
cp tbl_temp.frm tbl.frm
 
-- mysql移除讀鎖
unlock tables;
 
-- 測試修改是否成功
select * from tbl limit 1;
 
-- 如果出現錯誤,導致連接丟失等,可以回滾
flush tables with write lock;
cp tbl.frm.bak tbl.frm
unlock tables;

手動執行COPY方式

通過復制臨時表,然后修改臨時表,再把原表中的數據復制到臨時表中,並切換臨時表和原表。

當需要對原表中數據進行額外的處理時,只能選擇此方式,該方式會造成大量的磁盤IO,並且執行期間不允許寫入。

對於千萬級別的表,可以分批進行復制,使用一些策略來允許遷移過程中的寫入。

執行修改時需要考慮的因素

首先需要對執行的表數據量進行確認,如果數據量超過百萬級甚至千萬級,需要檢查下面的事項:

  • 當前系統內存容量充足
  • 當前系統內存使用情況良好
  • 當前系統CPU使用空閑
  • 執行修改期間是否允許停止服務
  • 是否有其他關聯的數據庫,保證數據一致性

如果有一些遺漏的點,歡迎大家補充,這樣也能夠在以后遇到類似問題的時候提供參考。

文章已經同步到個人博客:http://uusama.com/866.html,過兩天把其他的Online DDL操作測試之后再加上。(個把月沒寫文章了,已然懶得像一坨咸魚)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM