博客 / 詳情

返回

踩坑記錄:生產環境MySQL遷移那些事

上個月接到個任務,把公司的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,上海訪問不了。

試了幾個方案:

  1. 開公網 - 安全風險大,被否了
  2. VPN - 配置複雜,而且VPN帶寬不夠
  3. 專線 - 太貴,臨時遷移不值當

最後用的組網軟件(星空組網),把兩邊服務器組到一個虛擬局域網裏:

北京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遷移,建議先在測試環境完整跑一遍,把坑踩完再上生產。


有問題歡迎評論區交流。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.