上個月接到個任務,把公司的MySQL從阿里雲遷移到自建機房。聽起來簡單,mysqldump導出導入不就完了?
結果折騰了整整一週,踩了無數坑。記錄一下,給後來人避雷。
背景
原來的數據庫在阿里雲RDS,配置是4核8G,數據量大概200G。要遷移到公司自建機房的物理服務器上。
要求:
- 停機時間控制在30分鐘內
- 不能丟數據
- 遷移後性能不能下降
第一個坑:mysqldump太慢了
最開始想的最簡單,直接mysqldump:
mysqldump -h rds.xxx.com -u root -p --all-databases > all.sql
跑了6個小時還沒導完...
200G的數據,單線程dump,速度大概10MB/s。算了一下,導出要6小時,導入至少也要4-5小時。光這一步就10多個小時,停機30分鐘?做夢。
解決:mydumper多線程導出
# 安裝mydumper
apt install mydumper
# 多線程導出(8線程)
mydumper -h rds.xxx.com -u root -p password \
-t 8 \
-c \
-o /backup/dump
# 多線程導入
myloader -h localhost -u root -p password \
-t 8 \
-d /backup/dump
8線程跑,導出時間從6小時降到了50分鐘。導入也快了很多。
第二個坑:字符集不一致
數據導入後,發現有些中文變成了問號。
排查了半天,發現阿里雲RDS默認是utf8mb4,我們自建的MySQL是utf8。
-- 檢查字符集
SHOW VARIABLES LIKE 'character%';
阿里雲:
character_set_database: utf8mb4
character_set_server: utf8mb4
自建:
character_set_database: utf8
character_set_server: utf8
解決:統一字符集
# my.cnf
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
[client]
default-character-set=utf8mb4
然後重新導入。這次中文正常了。
第三個坑:自增ID衝突
導入完成後,應用跑起來,幾分鐘後報錯:
Duplicate entry '12345' for key 'PRIMARY'
自增ID衝突了?
原因是導入時沒有導入AUTO_INCREMENT的值,新插入的數據從1開始自增,和原有數據衝突了。
解決:導出時帶上自增值
mydumper -h rds.xxx.com -u root -p password \
-t 8 \
--set-names=utf8mb4 \
--triggers \
--routines \
--events \
-o /backup/dump
或者手動修復:
-- 查看當前最大ID
SELECT MAX(id) FROM users;
-- 結果:98765
-- 設置AUTO_INCREMENT
ALTER TABLE users AUTO_INCREMENT = 100000;
第四個坑:binlog位置找不到
遷移過程中,數據還在持續寫入。導出時記錄了binlog位置,準備用binlog追數據。
但是發現阿里雲RDS的binlog只保留7天,而且位置格式和自建MySQL不一樣。
解決:用GTID
還好阿里雲RDS支持GTID,改用GTID來追數據:
# 導出時記錄GTID
mydumper ... --set-names=utf8mb4
# 查看導出的metadata文件,裏面有GTID信息
cat /backup/dump/metadata
導入後,配置從庫從主庫同步:
CHANGE MASTER TO
MASTER_HOST='rds.xxx.com',
MASTER_USER='repl',
MASTER_PASSWORD='password',
MASTER_AUTO_POSITION=1;
START SLAVE;
這樣新數據就能自動同步過來了。
第五個坑:外鍵約束導入失敗
有幾張表死活導不進去,報錯:
Cannot add or update a child row: a foreign key constraint fails
外鍵約束的表,導入順序不對就會失敗。
解決:臨時關閉外鍵檢查
SET FOREIGN_KEY_CHECKS=0;
-- 導入數據
source /backup/dump.sql
SET FOREIGN_KEY_CHECKS=1;
或者用myloader時加參數:
myloader -h localhost -u root -p password \
-t 8 \
--overwrite-tables \
-d /backup/dump
myloader會自動處理外鍵順序。
第六個坑:遷移後查詢變慢
數據遷移完成,應用跑起來,發現有些查詢特別慢。
原來在阿里雲RDS上只要100ms的查詢,現在要5秒。
用EXPLAIN看了一下,發現沒走索引。再一看,索引是有的,但是沒生效。
原因:統計信息過期
數據導入後,表的統計信息還是舊的,優化器選錯了執行計劃。
-- 更新統計信息
ANALYZE TABLE users;
ANALYZE TABLE orders;
-- 或者批量更新所有表
mysqlcheck -u root -p --analyze --all-databases
跑完之後,查詢速度恢復正常。
第七個坑:時區問題
有些時間字段差了8小時。
阿里雲RDS默認時區是Asia/Shanghai,我們自建的是UTC。
-- 檢查時區
SHOW VARIABLES LIKE '%time_zone%';
解決:統一時區
# my.cnf
[mysqld]
default-time-zone = '+08:00'
最終的遷移方案
踩完這些坑,最後總結出來的遷移步驟:
1. 準備階段(T-1天)
# 在新服務器上配置好MySQL,確保:
# - 字符集:utf8mb4
# - 時區:+08:00
# - 開啓GTID
2. 全量同步
# mydumper導出
mydumper -h rds.xxx.com -u root -p password \
-t 8 \
--set-names=utf8mb4 \
--triggers --routines --events \
-o /backup/dump
# myloader導入
myloader -h localhost -u root -p password \
-t 8 \
--overwrite-tables \
-d /backup/dump
3. 增量同步
-- 配置主從複製
CHANGE MASTER TO
MASTER_HOST='rds.xxx.com',
MASTER_USER='repl',
MASTER_PASSWORD='password',
MASTER_AUTO_POSITION=1;
START SLAVE;
-- 確認同步狀態
SHOW SLAVE STATUS\G
-- Seconds_Behind_Master: 0 表示追上了
4. 切換(停機時間)
# 1. 應用停寫(或切只讀)
# 2. 等待從庫追上(Seconds_Behind_Master=0)
# 3. 停止複製
STOP SLAVE;
# 4. 修改應用配置,連接新數據庫
# 5. 應用恢復
實際停機時間:不到10分鐘。
異地遷移的額外問題
後來又做了一次跨城市的遷移,從北京機房到上海機房。
遇到的問題是:兩個機房網絡不通,北京是內網IP,上海訪問不了。
試了幾個方案:
- 開公網 - 安全風險大,被否了
- VPN - 配置複雜,而且VPN帶寬不夠
- 專線 - 太貴,臨時遷移不值當
最後用的組網軟件(星空組網),把兩邊服務器組到一個虛擬局域網裏:
北京MySQL:10.26.0.1:3306
上海服務器:10.26.0.2
# 上海服務器直接連北京的MySQL
mysql -h 10.26.0.1 -u root -p
這樣主從複製的配置也不用改,直接用內網IP就行。遷移完成後把組網關掉就行。
總結
MySQL遷移看似簡單,實際坑不少:
| 坑 | 解決方案 |
|---|---|
| mysqldump慢 | mydumper多線程 |
| 字符集不一致 | 統一utf8mb4 |
| 自增ID衝突 | 導出時帶AUTO_INCREMENT |
| binlog追不上 | 用GTID |
| 外鍵導入失敗 | 關閉外鍵檢查 |
| 查詢變慢 | ANALYZE TABLE |
| 時區問題 | 統一時區設置 |
| 跨網絡遷移 | 組網打通 |
如果你也要做MySQL遷移,建議先在測試環境完整跑一遍,把坑踩完再上生產。
有問題歡迎評論區交流。