作者:任坤
一、背景
某天晚上 20:00 左右開發人員找到我,要求把 pre-prod 環境上的某張表導入到 prod ,第二天早上 7 點上線要用。
該表有數億條數據,壓縮後 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 文件頁進行一下校驗,即可完成數據同步操作。
具體操作步驟如下:
- 目標庫,創建表結構,然後執行 ALTER TABLE t DISCARD TABLESPACE ,此時表t只剩下 frm 文件
- 源庫,開啓 2 個會話
session1:執行 FLUSH TABLES t FOR EXPORT ,該命令會對 t 加鎖,將t的髒數據從 buffer pool 同步到表文件,同時新生成 1 個文件 t.cfg ,該文件存儲了表的數據字典信息
session2:保持 session1 打開狀態,此時將 t.cfg 和 t.ibd 遠程傳輸到目標庫的數據目錄,如果目標庫是主從結構,需要分別傳輸到主從兩個實例,傳輸完畢後修改屬主爲 mysql:mysql
- 源庫,session1 執行 unlock tables ,解鎖表 t ,此時 t 恢復正常讀寫
- 目標庫,執行 ALTER TABLE t IMPORT TABLESPACE ,如果是主從結構,只需要在主庫執行即可
三、實測
針對該表,執行 ALTER TABLE ... IMPORT TABLESPACE 命令只需要 6 分鐘完成,且 IO 消耗和主從延遲都被控制到合理範圍。
原本需要數個小時的操作,只需 10 多分鐘完成(算上數據傳輸耗時)。
如果線上有空表需要一次性加載大量數據,可以考慮先將數據導入到測試環境,然後通過可傳輸表空間技術同步到線上,可節約大量執行時間和服務器資源。
四、總結
可傳輸表空間,有如下使用限制:
- 源庫和目標庫版本一致
- 只適用於 innodb 引擎表
- 源庫執行 flush tables t for export 時,該表會不可寫