作者簡介
任坤,現居珠海,先后擔任專職 Oracle 和 MySQL DBA,現在主要負責 MySQL、mongoDB 和 Redis 維護工作。
一、背景
某天晚上 20:00 左右開發人員找到我,要求把 pre-prod 環境上的某張表導入到 prod ,第二天早上 07:00 上線要用。該表有數億條數據,壓縮后 ibd 文件大約 25G 左右,表結構比較簡單:
CREATE TABLE `t` (
`UNIQUE_KEY` varchar(32) NOT NULL,
`DESC` varchar(64) DEFAULT NULL ,
`NUM_ID` int(10) DEFAULT '0' ,
PRIMARY KEY (`UNIQUE_KEY`),
KEY `index_NumID` (`NUM_ID`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED
MySQL 版本:pre-prod 和 prod 都采用 5.7.25 ,單向主從結構。
二、解決方案
最簡單的方法是采用 mysqldump + source ,但是該表數量比較多,之前測試的時候至少耗時 4h+ ,這次任務時間窗口比較短,如果中間執行失敗再重試,可能會影響業務正式上線。采用 select into outfile + load infile 會快一點,但是該方案有個致命問題:該命令在主庫會把所有數據當成單個事務執行,只有把數據全部成功插入后,才會將 binlog 復制到從庫,這樣會造成從庫嚴重延遲,而且生成的單個 binlog 大小嚴重超標,在磁盤空間不足時可能會把磁盤占滿。經過比較,最終采用了可傳輸表空間方案,MySQL 5.6 借鑒 Oracle 引入該技術,允許在 2 個不同實例間快速的 copy innodb 大表。該方案規避了昂貴的 sql 解析和 B+tree 葉節點分裂,目標庫可直接重用其他實例已有的 ibd 文件,只需同步一下數據字典,并對 ibd 文件頁進行一下校驗,即可完成數據同步操作。
具體操作步驟如下:1. 目標庫,創建表結構,然后執行 ALTER TABLE t DISCARD TABLESPACE ,此時表t只剩下 frm 文件
2. 源庫,開啟 2 個會話
session1:執行 FLUSH TABLES t FOR EXPORT ,該命令會對 t 加鎖,將t的臟數據從 buffer pool 同步到表文件,同時新生成 1 個文件 t.cfg ,該文件存儲了表的數據字典信息
session2:保持 session1 打開狀態,此時將 t.cfg 和 t.ibd 遠程傳輸到目標庫的數據目錄,如果目標庫是主從結構,需要分別傳輸到主從兩個實例,傳輸完畢后修改屬主為 mysql:mysql
3. 源庫,session1 執行 unlock tables ,解鎖表 t ,此時 t 恢復正常讀寫
4. 目標庫,執行 ALTER TABLE t IMPORT TABLESPACE ,如果是主從結構,只需要在主庫執行即可
三、實測
針對該表,執行 ALTER TABLE ... IMPORT TABLESPACE 命令只需要 6 分鐘完成,且 IO 消耗和主從延遲都被控制到合理范圍。原本需要數個小時的操作,只需 10 多分鐘完成(算上數據傳輸耗時)。如果線上有空表需要一次性加載大量數據,可以考慮先將數據導入到測試環境,然后通過可傳輸表空間技術同步到線上,可節約大量執行時間和服務器資源。
四、總結
可傳輸表空間,有如下使用限制:源庫和目標庫版本一致
只適用于 innodb 引擎表
源庫執行 flush tables t for export 時,該表會不可寫