凌晨两点,你盯着屏幕上不断跳动的传输进度,几百个G的MySQL数据要从A地机房迁到B地机房,窗口只有四个小时。合同上明明写着100Mbps带宽,可实际传输速度就是跑不满,眼看着剩余时间一点点被吃掉,那种感觉只要经历过一次就忘不掉。跨地区数据库迁移最让人头疼的地方就在于:真正卡住你的往往不是数据库本身,而是网络这堵墙,以及你对待这堵墙的方式。这篇文章不聊MySQL安装配置,也不扯存储引擎底层原理,就聚焦一件事:跨地区MySQL数据迁移时,如何用数据压缩和带宽限制把速度真正优化上去,让迁移从"听天由命"变成"可控可算"。
1. 把耗时账先算明白:带宽、延迟和传输量到底谁在拖后腿
1.1 迁移时间不是"数据量÷带宽"这么简单
很多人一开始都习惯拿数据总量除以带宽来估算时长,比如300GB数据、100Mbps带宽,一算觉得“顶多六七个小时”。但实际一跑就傻眼了:一小时才传了几十个GB,跟预期差了一大截。问题就出在“跨地区”这三个字上。
跨地域的网络链路并不是你独享的物理管道,数据要经过多段路由、多个运营商节点,延迟和丢包都会直接影响TCP的传输效率。TCP单个连接的吞吐量受往返时延(RTT)和拥塞窗口共同限制,当RTT很高时,窗口增长得慢,单条TCP流根本打不满带宽。比如同样是100Mbps链路,同城可能轻松跑到90Mbps以上,跨地域可能只能跑三四十Mbps,丢包一高甚至更低。
所以在规划迁移之前,先别急着算理论值,一定要用工具实测两端之间的真实吞吐。我的习惯是拿一小段数据先"探路":
iperf3 -c 目标IP -P 4 -t 60这个命令会用4个并发流测60秒,能直观看到多流并发时能达到多少带宽。如果单流只有30Mbps,四流能到80Mbps,那就说明单连接是瓶颈,后面迁移时应该适当增加并发。如果四流也上不去,那就要考虑链路本身的质量和限速策略了。
1.2 一个具体的耗时测算例子
拿一个我实际遇到过的场景来算账。当时要从一个老机房迁到另一个区域的云上实例,逻辑导出的SQL文件总共300GB,用工具实测下来源端到目标端的有效吞吐大概是6MB/s,约等于48Mbps,离合同带宽差得很远。
- 不压缩直接传:300 × 1024 ÷ 6 ÷ 3600 ≈ 14.2小时
- 压缩比3:1后传输量变成100GB:100 × 1024 ÷ 6 ÷ 3600 ≈ 4.7小时
- 再把并发流从1提到4,如果链路余量足够,时间还能进一步缩短
从这个例子就能看出来,跨地区迁移时"数据压缩"往往是性价比最高的优化手段。它不是赌网络质量,而是直接减少需要传输的字节数。带宽越窄,压缩收益越明显;带宽很充裕时,压缩的CPU开销反而可能成为新的瓶颈,所以要动态取舍。
1.3 备份与迁移要把CPU、IO开销一起算进去
压缩不是免费的,它消耗源机的CPU,目标端解压还要额外消耗目标机的CPU和磁盘IO。如果源库本身就是高负载的线上实例,压缩级别调得太高,可能会影响业务。解决思路有两个:一是把压缩级别调低,让压缩速度跑起来;二是用专门的备份机或从库来承担导出和压缩工作。后面章节会详细聊算法选型,这里先记住一个原则:跨地区迁移的性能优化,本质是CPU、带宽、磁盘IO三者之间的平衡,哪个是短板就先补哪个。
2. 压缩算法怎么选:gzip、zstd、lz4 的迁移实测取舍
2.1 先搞清楚"压缩比"在数据库数据上到底有多高
压缩效果和数据类型关系很大。MySQL逻辑备份出来的内容绝大多数是INSERT语句,字段名重复率高、数值和字符串也有大量重复模式,这类文本的压缩比往往能达到3:1甚至更高。物理备份则不一样,InnoDB的物理文件里包含已分配但未使用的页、页内碎片,压缩比通常不如SQL文本那么稳定,但依然有明显收益。
我自己的测试数据集一般取实际业务库的一个中间表集合,大约8GB,包含订单、用户、日志三类表。这样测出来的压缩比相对有代表性,也能看出不同算法在真实数据上的表现差异。如果手头没有现成数据集,建议先用mysqldump导一小部分真实数据压一下看效果,千万别拿全0的测试文件来评估压缩比,那是自欺欺人。
2.2 gzip:工具链成熟,但千万别顺手就上 -9
gzip 是大多数人最先想到的方案,因为mysqldump加gzip的组合几乎不需要额外安装任何东西:
mysqldump --single-transaction --set-gtid-purged=OFF -A | gzip -1 > dump.sql.gz但这里有个非常容易踩的坑:有人习惯性用 gzip -9 追求最高压缩率,结果发现压缩速度直线下降,整个迁移的瓶颈从网络变成了CPU。跨地区迁移场景下,压缩率稍微低一点没关系,压缩速度太慢才是大问题。
实测里 gzip -1 比 -9 的压缩速度能快好几倍,压缩率只差10%左右。如果机器有多核,还可以用 pigz 并行压缩:
mysqldump --single-transaction -A | pigz -p 8 > dump.sql.gz2.3 zstd:跨地区迁移场景里最推荐的主流选择
zstd是Facebook开源的压缩算法,如今在备份传输领域基本成了默认选项。它的特点是压缩率和gzip高层级差不多,但压缩速度快得多。用zstd处理8GB的SQL导出数据,默认级别下压缩吞吐能达到gzip -6的两倍以上,压缩比还略高。
我目前最常用的组合是这样:
mysqldump --single-transaction --set-gtid-purged=OFF -A | zstd -3 -T0 > dump.sql.zst恢复时对应:
zstd -dc dump.sql.zst | mysql这里的 -3 是压缩级别,-T0 表示自动使用所有CPU核心。如果你对压缩率有更高要求,可以调到 -9 甚至更高,但跨地区场景下我一般推荐 -3 到 -6 之间,平衡性最好。zstd还自带一个好处:解压速度也快,目标端恢复时可以更快地进入导入阶段。
2.4 lz4:带宽不紧张的"快"选项
lz4 是极致追求压缩速度的算法,压缩吞吐可以达到gzip的5倍以上,但压缩比一般只有2:1左右。什么场景适合用它?两地之间有高速专线、带宽不是瓶颈,反而是源库CPU压力大、不想让压缩占用太多计算资源的时候,用lz4边压缩边传,总体时间可能最短。
我给出一个参考表,注意具体数值会随机器配置和数据特征变化:
| 压缩算法 | 常见级别 | 压缩比参考 | 压缩吞吐参考 | 适合场景 |
|---|---|---|---|---|
| gzip | -1 | 2.5~3.0:1 | 约100~150MB/s | 工具链要求高、只求稳 |
| gzip | -6 | 3.0~3.5:1 | 约60~100MB/s | 日常默认,速度一般 |
| zstd | -3 | 2.8~3.5:1 | 约200~350MB/s | 跨地区带宽有限时最推荐 |
| zstd | -9 | 3.2~4.0:1 | 约100~150MB/s | 带宽极窄但CPU有富余 |
| lz4 | -1 | 1.8~2.5:1 | 约600MB/s以上 | 高速专线+源库CPU吃紧 |
从表格可以看出,zstd在压缩率和压缩速度的平衡上确实最适合跨地区迁移。如果拿不准用哪个,直接上zstd -3基本不会错。
2.5 物理备份的压缩路径跟逻辑备份不一样
逻辑备份虽然通用,但大库导出导入非常耗时。跨地区迁移大库时,很多人改用Percona XtraBackup做物理备份,备份过程中就可以直接压缩:
xtrabackup --backup --compress=zstd --compress-threads=4 --target-dir=/data/backup备份出来的压缩文件需要先解压再恢复,不能直接当数据目录用:
xtrabackup --decompress --target-dir=/data/backup xtrabackup --prepare --target-dir=/data/backup物理备份的压缩有两个好处:一是备份阶段就压缩,传输前已经是压缩态;二是备份文件是原始数据页,恢复时不需要执行上千万条INSERT,导入速度快得多。代价是工具链更复杂,准备和校验的时间也要留足。
2.6 压缩过程中尽量用管道,避免"先压缩再传"的中间环节
我见过不少人把迁移做成了三步:先mysqldump导出原始文件,再gzip压缩,最后传文件。这样磁盘占用是原始加压缩两份,中间还多了两次全量读写,对时间和磁盘都是浪费。
正确做法是让数据"流"起来:
mysqldump --single-transaction -A | zstd -3 | ssh target 'cat > /data/dump.sql.zst'或者用netcat在目标端直接收流:
mysqldump --single-transaction -A | zstd -3 | nc 目标IP 9000这种方式能减少本地磁盘占用,也能避免二次读写消耗。缺点是如果传输中断,想要断点续传就麻烦,所以如果是跨公网长传,我更推荐后面会说到的rsync方案。
3. 限速不是拖后腿:业务与迁移共享链路的正确姿势
3.1 为什么必须做带宽限制
跨地区迁移时,很多时候业务流量和迁移流量走的是同一条公网出口,或者共用了同一条专线。如果不加限制直接全速传输,很容易把出口带宽占满,线上应用响应变慢,监控告警半夜响个不停,最后被迫中断迁移。所以限速的本质不是让迁移变慢,而是给迁移排一个合理的优先级:白天业务高峰时段压低迁移带宽,夜间窗口放开提速。
真正常见的做法绝不是"全速或者全停"两个极端,而是阶段式限速。比如白天限10Mbps,夜里放开到50Mbps,切换当天再给更高的临时额度。
3.2 rsync 的 bwlimit 参数,先把单位搞清楚
rsync是跨服务器传输文件的经典工具,优势是支持断点续传、增量同步,配合SSH还能加密传输。它的限速参数是 --bwlimit:
rsync -avP --bwlimit=4096 ./backup/ user@目标IP:/data/backup/这个参数踩坑率极高:--bwlimit 的单位是 KB/s,不是 Mb/s。--bwlimit=4096 表示限速到 4096KB/s,约4MB/s。很多人以为是4096Mbps,结果发现速度完全对不上。如果想把带宽限制在50Mbps,需要算成 50×1024÷8≈6400KB/s。
要注意的是,如果已经用zstd对数据做过外部压缩,rsync的 -z 参数就别再开了,否则等于对已经压缩过的文件再做一次压缩,白白消耗CPU。
3.3 用 tc 做更精细的流量整形
如果觉得rsync只能限制单个进程不够用,可以用Linux自带的tc命令做流量整形,精确到整个网卡或者某个目标IP。比如把eth0整个出口限到20Mbit:
tc qdisc add dev eth0 root tbf rate 20mbit burst 32kbit latency 400ms更精细的场景是源库主机上同时跑着多个迁移任务,只限制发往目标IP的流量:
tc qdisc add dev eth0 root handle 1: htb default 10 tc class add dev eth0 parent 1: classid 1:10 htb rate 20mbit tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 match ip dst 目标IP flowid 1:10tc的优点是很灵活,缺点也明显:命令是即时生效的,网络服务重启或者机器重启后配置会自动消失,需要固化到systemd服务或初始化脚本里。如果团队里网络功底一般,还是优先用工具自带的限速参数更省心。
3.4 分时段提速,比固定限速更实用
迁移窗口通常集中在深夜,所以分时段限速比固定限速实用得多。我一般用cron定时调整tc参数:
0 23 * * * tc qdisc change dev eth0 root tbf rate 50mbit burst 64kbit latency 400ms 0 6 * * * tc qdisc change dev eth0 root tbf rate 10mbit burst 64kbit latency 400ms如果你是云服务器,也可以直接利用云厂商的带宽管理功能或安全组限速策略,底层逻辑是一样的。反正目的就是在不影响业务的前提下,尽可能挤时间传输数据。
3.5 并发数必须跟着带宽上限走
很多人看到"多线程迁移"就直接把并发开到8个、16个,结果总吞吐超过链路容量,拥塞和重传反而拖慢了整体进度。正确做法是先测单流实际吞吐,再根据限速目标推算并发数:
并行度 ≈ 限速目标值 ÷ 单流实际吞吐
比如限制在50Mbps,单流稳定6MB/s,那么开2到4个并发就能打满余量,开8个只会徒增确认包和重传。这个原则在mysqldump多线程、mydumper、rsync多进程、XtraBackup并行流传输里都适用。
4. 压缩和限速之外,跨地区迁移实战里的隐藏提速点
4.1 导出别只用 mysqldump 单线程
mysqldump很好用,但它导出大库时是单线程,一条一条SELECT出来写文件,速度确实有限。如果库里有几千张表或者几张大表,导出环节就会拖后腿,这时候可以换成mydumper。
mydumper可以把表拆成多个chunk,多线程并行导出,并把每张表单独存成一个文件,方便按优先级传输和导入。导出时也可以配合压缩:
mydumper -u root -p xxx -B bizdb --threads=8 --compress -o /data/backup导入端用对应的myloader并行导入。要注意的是,并行度不是越高越好,导入端的目标库连接数上限、磁盘IO能力都要考虑进去,否则导入端本身会被压垮。
4.2 别让目标端导入变成新瓶颈
很多次排查下来,发现网络传输已经跑到计划值了,但目标端导入速度跟不上,整个迁移卡在"解压快、导入慢"的状态。这时候优先检查目标库的几个关键参数:
- innodb_buffer_pool_size 是否够大,最好能装下常用索引和热点数据
- 导入期间可以临时把 innodb_flush_log_at_trx_commit 设为0、sync_binlog设为0,减少磁盘同步次数,但注意导入完成后必须改回安全值
- 如果目标库还挂着级联复制,先暂停从库的SQL线程,或者调整并行复制线程数,避免导入抖动影响其他链路
大表导入还有一个常用技巧:先只建表结构,不建二级索引,数据导入完成后再批量创建索引。因为每插入一行数据都要同步维护索引,代价非常高,先传数据后建索引往往能快出好几倍。
4.3 增量同步:让停机窗口无限缩短
如果数据量太大,一次全量迁移想在一个窗口内完成根本不现实。这时候必须采用"全量+增量"的两阶段方案:
- 全量备份:用XtraBackup或mysqldump做全量备份,压缩传输到目标端
- 增量同步:源库开启binlog,并且格式设为ROW模式,用mysqlbinlog把新增的binlog持续同步到目标端
mysqlbinlog --read-from-remote-server --host=源库IP --user=repl --password=xxx \ --stop-never mysql-bin.000001 | mysql -h 目标库IP切换前对比binlog位点,确认目标库追平所有增量,然后短暂停写、切换、验证。这种方式能把实际不可用时间压缩到几分钟,代价是binlog传输本身也要占带宽,限速策略要把这部分流量一起算进去。
4.4 校验和断点:迁移最容易忽略的隐形时间成本
跨地区长传最怕两件事:传了一半断了,以及传完了发现数据不一致。rsync的 -P 参数可以在中断后继续传输,所以我很推荐用它来传备份文件。但如果用nc、socat这类裸流工具,中断就基本要重来,所以裸流只建议在内网或短链路场景用。
数据校验方面,pt-table-checksum是主流选择,它会在源库和目标库分别计算校验值再比对。但要注意它会扫描全表,对线上业务有压力,建议放在低峰期执行。如果时间紧张,至少也要抽几张关键表做行数和最大值、最小值的快速比对。
4.5 压缩与加密同时做,不要为了安全丢掉性能
跨地区公网传输时,敏感数据不能明文裸奔。用SSH通道或者TLS加密是必须的。很多人担心加密会影响传输速度,实际上现代CPU都支持AES-NI指令集,加密带来的性能损耗很小。比如rsync走SSH时可以指定加密算法:
rsync -avP -e 'ssh -c aes128-gcm@openssh.com' --bwlimit=6400 \ ./backup/ user@目标IP:/data/backup/如果嫌麻烦,直接默认SSH通道也没问题,别因为格式问题放弃加密。安全性永远值得那一点点性能损耗。
4.6 先演练一次小规模迁移
最后说一个我自己吃了不少亏才养成的习惯:大规模迁移前,先拿一个几GB的代表性子集走一遍完整流程,包括压缩、限速、传输、解压、导入、校验。这一步能验证所有命令参数是否合理,还能给你一个真实的"每秒多少MB"的基准值,用这个值去推算总时长,误差会小很多。
演练时重点观察目标端的CPU使用率和磁盘IO,如果解压导入阶段CPU已经飙到90%以上,说明网络传输不是瓶颈,目标端才是,后面正式迁移时要考虑加大目标端规格或者调整导入参数。实际跨地区项目里,很多问题不是参数不会配,而是没提前在目标端环境验证过解压和导入速度。一个几GB的演练,能帮你避免正式迁移时凌晨三点才发现跑不完的尴尬。