1. 程式人生 > >mysql大表在不停機的情況下增加欄位該怎麼處理

mysql大表在不停機的情況下增加欄位該怎麼處理

MySQL中給一張千萬甚至更大量級的表新增欄位一直是比較頭疼的問題,遇到此情況通常該如果處理?本文通過常見的三種場景進行案例說明。

1、 環境準備

資料庫版本: 5.7.25-28(Percona 分支)

伺服器配置:  3臺centos 7虛擬機器,配置均為2CPU  2G記憶體

資料庫架構: 1主2從的MHA架構(為了方便主從切換場景的演示,如開啟GTID,則兩節點即可),關於MHA搭建可參考此文 MySQL高可用之MHA叢集部署

準備測試表:  建立一張2kw記錄的表,快速建立的方法可以參考快速建立連續數

本次對儲存過程稍作修改,多新增幾個欄位,儲存過程如下:

DELIMITER $$
CREATE  PROCEDURE `sp_createNum`(cnt INT )
BEGIN
    DECLARE i INT  DEFAULT 1;
    DROP TABLE  if exists  tb_add_columns;
    CREATE TABLE if not exists tb_add_columns(id int primary key,col1 int,col2 varchar(32));
    INSERT INTO tb_add_columns(id,col1,col2) SELECT i  as id ,i%7 as col1,md5(i) as col2;
    
    WHILE i < cnt DO
      BEGIN
        INSERT INTO tb_add_columns(id,col1,col2) SELECT id + i   as id ,( id + i) %7 as col1,md5( id + i) as col2  FROM tb_add_columns WHERE id <=cnt - i ;
        SET i = i*2;
      END;
    END WHILE;
END$$
DELIMITER ;

呼叫儲存過程,完成測試表及測試資料的建立。

mysql> call sp_createNum(20000000);

 2.  直接新增欄位

使用場景: 在系統不繁忙或者該表訪問不多的情況下,如符合ONLINE DDL的情況下,可以直接新增。

模擬場景: 建立一個測試指令碼,每10s訪問該表隨機一條記錄,然後給該表新增欄位

訪問指令碼如下:

#!/bin/bash
# gjc

for i in  {1..1000000000}                    # 訪問次數1000000000,按需調整即可
do
    id=$RANDOM                          #生成隨機數    
    mysql -uroot -p'123456' --socket=/data/mysql3306/tmp/mysql.sock  -e "select  a.*,now() from  testdb.tb_add_columns a where id = "$id     # 訪問資料
    sleep 10                            #  暫停10s
done

執行指令碼

sh  test.sh

 給表新增欄位

mysql> alter table  testdb.tb_add_columns add col3 int;

  此時,訪問正常。

 附ONLINE DDL的場景如下,建議DBA們必須弄清楚

(圖片轉載於https://blog.csdn.net/finalkof1983/article/details/88355314)

 

 (圖片轉載於https://blog.csdn.net/finalkof1983/article/details/88355314)

3.   使用工具線上新增

雖然Online DDL新增欄位時,表依舊可以讀寫,但是生產環境使用場景中對大表操作使用最多的還是使用工具pt-osc或gh-ost新增。

本文主要介紹 pt-osc(pt-online-schema-change) 來新增欄位,該命令是Percona Toolkit工具中的使用頻率最高的一種

關於Percona Toolkit的安裝及主要使用可以參考  五分鐘學會Percona Toolkit 安裝及使用

新增欄位

root@mha1 ~]# pt-online-schema-change --alter "ADD COLUMN  col4  int" h=localhost,P=3306,p=123456,u=root,D=testdb,t=tb_add_columns,S=/data/mysql3306/tmp/mysql.sock  --charset=utf8mb4 --execute

主要過程如下:

1> Cannot connect to A=utf8mb4,P=3306,S=/data/mysql3306/tmp/mysql.sock,h=192.168.28.132,p=...,u=root
1> Cannot connect to A=utf8mb4,P=3306,S=/data/mysql3306/tmp/mysql.sock,h=192.168.28.131,p=...,u=root
No slaves found.  See --recursion-method if host mha1 has slaves.  #  因為使用的是socket方式連線資料庫 且未配置root遠端連線賬號,所以會有此提示

# A software update is available:
Operation, tries, wait:
  analyze_table, 10, 1                                     
  copy_rows, 10, 0.25                                       
  create_triggers, 10, 1                      
  drop_triggers, 10, 1
  swap_tables, 10, 1
  update_foreign_keys, 10, 1
Altering `testdb`.`tb_add_columns`...
Creating new table...                                     #  建立中間表,表名為"_原表名_new"
Created new table testdb._tb_add_columns_new OK.           
Altering new table...                                     #  修改表,也就是在新表上新增欄位,因新表無資料,因此很快加完
Altered `testdb`.`_tb_add_columns_new` OK.                  
2020-06-20T12:23:43 Creating triggers...                  #  建立觸發器,用於在原表拷貝到新表的過程中原表有資料的變動(新增、修改、刪除)時,也會自動同步至新表中
2020-06-20T12:23:43 Created triggers OK.
2020-06-20T12:23:43 Copying approximately 19920500 rows... # 拷貝資料,資料庫量是統計資訊裡的,不準確
Copying `testdb`.`tb_add_columns`:  11% 03:50 remain       #  分批拷貝資料(根據表的size切分每批拷貝多少資料),拷貝過程中可以用show processlist看到對應的sql
Copying `testdb`.`tb_add_columns`:  22% 03:22 remain
Copying `testdb`.`tb_add_columns`:  32% 03:10 remain
Copying `testdb`.`tb_add_columns`:  42% 02:45 remain
Copying `testdb`.`tb_add_columns`:  51% 02:21 remain
Copying `testdb`.`tb_add_columns`:  62% 01:48 remain
Copying `testdb`.`tb_add_columns`:  72% 01:21 remain
Copying `testdb`.`tb_add_columns`:  81% 00:53 remain
Copying `testdb`.`tb_add_columns`:  91% 00:24 remain
2020-06-20T12:28:40 Copied rows OK.                       # 拷貝資料完成
2020-06-20T12:28:40 Analyzing new table...                # 優化新表
2020-06-20T12:28:40 Swapping tables...                    # 交換表名,將原表改為"_原表名_old",然後把新表表名改為原表名
2020-06-20T12:28:41 Swapped original and new tables OK.    
2020-06-20T12:28:41 Dropping old table...                 #  刪除舊錶(也可以新增引數不刪除舊錶)
2020-06-20T12:28:41 Dropped old table `testdb`.`_tb_add_columns_old` OK.
2020-06-20T12:28:41 Dropping triggers...                  # 刪除觸發器
2020-06-20T12:28:41 Dropped triggers OK.
Successfully altered `testdb`.`tb_add_columns`.            # 完成

修改過程中,讀寫均不受影響,大家可以寫個程式包含讀寫的

注:  無論是直接新增欄位還是用pt-osc新增欄位,首先都得拿到該表的元資料鎖,然後才能新增(包括pt-osc在建立觸發器和最後交換表名時都涉及),因此,如果一張表是熱表,讀寫特別頻繁或者新增時被其他會話佔用,則無法新增。

例如: 鎖住一條記錄

用pt-osc新增欄位,會發現一直卡在建立觸發器那一步

 此時檢視對應的SQL正在等待獲取元資料鎖

換成直接新增也一樣,例如

 當達到鎖等待後將會報錯放棄新增欄位

mysql> alter table  testdb.tb_add_columns add col5 int;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

對於此情況,需等待系統不繁忙情況下新增,或者使用後續的在從庫建立再進行主從切換

4  先在從庫修改,再進行主從切換

使用場景: 如果遇到上例中一張表資料量大且是熱表(讀寫特別頻繁),則可以考慮先在從庫新增,再進行主從切換,切換後再將其他幾個節點上新增欄位。

先在從庫新增(本文在備選節點新增)

mysql> alter table  testdb.tb_add_columns add col5 int;
Query OK, 0 rows affected (1 min 1.91 sec)
Records: 0  Duplicates: 0  Warnings: 0

進行主從切換

使用MHA指令碼進行線上切換

masterha_master_switch  --conf=/etc/masterha/app1.conf --master_state=alive  --orig_master_is_new_slave --new_master_host=192.168.28.131  --new_master_port=3306

切換完成後再對其他節點新增欄位

/* 原主庫上新增192.168.28.128  */
mysql>  alter table  testdb.tb_add_columns add col5 int;
Query OK, 0 rows affected (1 min 8.36 sec)
Records: 0  Duplicates: 0  Warnings: 0

/* 另一個從庫上新增192.168.28.132  */
mysql>  alter table  testdb.tb_add_columns add col5 int;
Query OK, 0 rows affected (1 min 8.64 sec)
Records: 0  Duplicates: 0  Warnings: 0

這樣就完成了欄位新增。

5.  小結

生產環境MySQL新增或修改欄位主要通過如下三種方式進行,實際使用中還有很多注意事項,大家要多多總結。

  • 直接新增

如果該表讀寫不頻繁,資料量較小(通常1G以內或百萬以內),直接新增即可(可以瞭解一下online ddl的知識)

  •  使用pt_osc新增

如果表較大 但是讀寫不是太大,且想盡量不影響原表的讀寫,可以用percona tools進行新增,相當於新建一張添加了欄位的新表,再降原表的資料複製到新表中,複製歷史資料期間的資料也會同步至新表,最後刪除原表,將新表重新命名為原表表名,實現欄位新增

  •  先在從庫新增 再進行主從切換

如果一張表資料量大且是熱表(讀寫特別頻繁),則可以考慮先在從庫新增,再進行主從切換,切換後再將其他幾個節點上新增欄位