文章

MySQL 数据迁移(二)原生 mysqldump 导出导入实战与评测

mysqldump 是 MySQL 自带的逻辑迁移工具,人人都会用,但真正理解它在压测下对源库的冲击、导入慢的根因、以及适用数据量级边界的人不多。本篇在 2000 万行真实数据集上完整实战 mysqldump 导出 → 压缩传输 → mysql source 导入全流程,边迁移边压测,用数据回答:它到底能扛多大数据量。

MySQL 数据迁移(二)原生 mysqldump 导出导入实战与评测

文章信息

  • 原文链接:https://jiayq.blog.csdn.net/article/details/166593481
  • 发布时间:2026-09-24 15:04:12
  • 标签:#mysql, #数据库, #mysqldump, #mydumper, #myloader, #DTS

MySQL 数据迁移(二)原生 mysqldump 导出导入实战与评测

摘要

mysqldump 是 MySQL 自带的逻辑迁移工具,人人都会用,但真正理解它在压测下对源库的冲击、导入慢的根因、以及适用数据量级边界的人不多。本篇在 2000 万行真实数据集上完整实战 mysqldump 导出 → 压缩传输 → mysql source 导入全流程,边迁移边压测,用数据回答:它到底能扛多大数据量。

1. 建议先看

本篇是《MySQL 数据迁移》系列第二篇,评测环境(单节点 / 主从 / 读写分离三套架构)、数据集(sysbench 2000 万行)和评测指标定义都在第一篇:

2. mysqldump 的工作原理

2.1 它本质上是一个”大号 SELECT 客户端”

mysqldump 是 MySQL 官方自带的客户端工具,逻辑导出的本质:连接源库 → 按表执行 SELECT * → 把结果拼成 INSERT 语句写进文本文件。它不碰数据文件、不碰存储引擎内部结构,所以导出产物是纯 SQL 文本,跨版本、跨平台兼容性最好。

2.2 一致性是怎么保证的:–single-transaction

导出 20 张表需要时间,导出期间业务还在写,凭什么导出来的数据是同一时刻的?靠的是 --single-transaction

1
2
3
4
5
-- mysqldump --single-transaction 内部等价于:
START TRANSACTION WITH CONSISTENT SNAPSHOT;  -- 建立一致性读视图
SELECT * FROM sbtest1;  -- 表1的数据,永远看到事务开始时刻的版本
SELECT * FROM sbtest2;  -- 表2同上
...

InnoDB 的 MVCC 机制保证:快照建立后,后续所有 SELECT 都读到同一时刻的数据版本,期间业务的新写入被隔离在快照之外。注意两个坑

  1. 只对 InnoDB 表有效。如果有 MyISAM 表,--single-transaction 保护不了它,需要 --lock-all-tables(锁全库,业务停摆)
  2. 长事务的代价:快照期间源库的写操作会产生 undo 日志版本链,导出越慢、业务写入越猛,undo 膨胀越厉害(第四节实测)

2.3 extended insert:一条 INSERT 装一火车的值

mysqldump 默认开启 --extended-insert(8.0 中已不可关闭),导出产物长这样:

1
INSERT INTO `sbtest1` VALUES (1,499504,'...', '...'),(2,502015,'...','...'),(3,...),...;

一条 INSERT 挂几百上千个 value 元组,而不是每行一条。这个设计决定了导出文件的”批次”大小由 net_buffer_length(默认 1MB)控制,也直接影响导入速度——逐行 INSERT 和批量 INSERT 的导入性能差几倍。

2.4 位点记录:–source-data 与 GTID

全量导出只是迁移第一步,导出后源库还有增量写入,需要从某个位点开始追 binlog。--source-data=2 会把导出时刻的 binlog 位点写进产物(8.0.22+ 用 --source-data,老版本叫 --master-data=2 表示注释掉不执行):

1
2
3
4
--
-- source to log replication position to file
-- POSITION  = binlog file and position of the snapshot
CHANGE REPLICATION SOURCE TO SOURCE_LOG_FILE='mysql-bin.000007', SOURCE_LOG_POS=157;

这个位点就是后续增量追赶的起点。注意:从主库导出才能拿到这个位点;从只读副本导出时副本自身通常不开 binlog,--source-data 会直接失败——这是”从从库/只读节点导出”方案的一个注意点,第三篇会展开。

file+pos 位点的粒度其实有点粗,GTID 才是最精确的

SOURCE_LOG_FILE + SOURCE_LOG_POS 只是 binlog 字节流里的一个偏移量:它不表达”这个位点之前包含哪些事务”,而且很脆弱——主从切换后新主上的 file/pos 完全对不上旧主的、binlog 轮转清理后按位点接复制容易错位、手工指定位点时差一个事务就会丢数据或重复。GTID 则把位点精确到事务粒度:导出时刻的 gtid_executed 是一个”已执行事务集合”(uuid:1-N),增量追赶时 SOURCE_AUTO_POSITION=1 让源库根据这个集合自动协商从哪里发,集合里已有的事务幂等跳过——不重不漏,天然容错。

mysqldump 在 GTID 环境下的产物头部会带 SET @@GLOBAL.gtid_purged='uuid:1-N'(即 5.5 节方案 B 的机制),它记录的就是快照时刻的事务集合;也可以在导出完成时立即执行 SHOW MASTER STATUSExecuted_Gtid_Set 单独记录。

生产结论:能开 GTID 就用 GTID 管位点,file+pos 是没有 GTID 的老版本环境下的兜底(第一篇 3.2 节讲过 binlog + GTID 是生产级迁移的前提)。

3. 实战:完整迁移流程

以下命令全部在真实环境执行过,环境说明见第一篇(脱敏处理)。

3.1 导出

1
2
3
4
5
6
7
# 标准导出命令(InnoDB + GTID 环境)
docker exec mysql-source sh -c "mysqldump -uroot -p'***' \
  --single-transaction \
  --source-data=2 \
  --set-gtid-purged=OFF \
  --triggers --routines --events \
  --databases sbtest" > dump_single.sql

参数逐个说清楚:

参数作用不加的后果
--single-transaction一致性快照导出,不锁表用默认的 --lock-tables,导出期间锁表业务停摆
--source-data=2记录 binlog 位点到产物(GTID 环境下同时有事务集合可记,见 2.4)后续增量追赶找不到起点
--set-gtid-purged=OFF产物不含 SET @@GLOBAL.gtid_purged目标库已有 GTID 历史时防报错/防污染;若目标库是全新空实例,生产上推荐保留默认让源库 GTID 接管(方案选择详见 5.5)
--triggers --routines --events导出触发器/存储过程/事件触发器和存储过程丢失,业务出问题
--quick流式逐行读,不缓存整表到内存默认已开,但显式写出防被配置覆盖

实测数据(2000 万行 / 20 张表,数据文件约 4.2GB,压测流量伴随):

指标数值
导出耗时60~66 秒
产物大小3810 MB(.sql 文本)
源库压测 TPS(导出期间)286(基线 261,详见 4.2 节分析)

单看导出不算慢——mysqldump 的真正瓶颈在导入侧(见 4.1 导入数据)。

3.2 导出产物长什么样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ head -30 dump_single.sql
-- MySQL dump 10.13  Distrib 8.0.46, for Linux (x86_64)
...
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
...(一堆环境恢复用 SET 语句)
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `sbtest`;
USE `sbtest`;
--
-- Table structure for table `sbtest1`
--
DROP TABLE IF EXISTS `sbtest1`;      -- 注意:默认先 DROP!
CREATE TABLE `sbtest1` (
  `id` int NOT NULL AUTO_INCREMENT,
  ...
) ENGINE=InnoDB AUTO_INCREMENT=1000001 DEFAULT CHARSET=utf8mb4;
--
-- Dumping data for table `sbtest1`
--
INSERT INTO `sbtest1` VALUES (1,...),(2,...),...

两个值得注意的细节:

  1. 产物默认包含 DROP TABLE IF EXISTS:往已有数据的库导入会先删表!同构迁移(源目标都有同名表)时要非常小心,必要时加 --skip-add-drop-table
  2. --databases 会带出 CREATE DATABASE:不加 --databases 只导表,导入时需要目标库先存在

3.3 压缩与传输

裸 SQL 文本压缩率很高。实测三种压缩工具对比(同一份 3810MB 的产物):

工具算法耗时产物大小压缩比
gzipDEFLATE(单线程)329s1841MB0.46
pigzDEFLATE(多线程)43s1843MB0.46
qpressQuickLZ23s2610MB0.65

三个工具三种取舍:qpress 最快但压缩率最差(QuickLZ 是为速度设计的算法);pigz 速度是 gzip 的 7.6 倍,压缩率几乎无损(多核并行切分压缩);gzip 单线程在现代多核机器上没有任何理由再用。SQL 文本重复度高,gzip 系能压到 46%,传输带宽直接省一半多。

这里澄清一个高频混淆:xbstreamqpress 经常成对出现,但它们是 XtraBackup 物理备份体系的工具——xbstream 是 XtraBackup 的流式归档格式(把数据文件打包成流),qpress 是配套的压缩器。mysqldump 的 SQL 文本产物用 gzip/pigz/qpress 压缩都可以,xbstream 在这个场景完全用不上。物理备份的 xbstream + qpress 组合实战在第四篇。

传输环节,按 100Mbps(12.5MB/s)跨机房带宽换算传输时间:

产物大小100Mbps 传输500Mbps 传输
裸 SQL3810MB约 5.1 分钟约 61 秒
gzip/pigz1841MB约 2.5 分钟约 30 秒
qpress2610MB约 3.5 分钟约 42 秒

跨机房场景先压缩再传输是基本素养,pigz 是首选。

3.4 导入:mysql source / 重定向

1
2
3
4
5
# 方式一:重定向(推荐脚本使用)
mysql -h*** -P13311 -uroot -p*** sbtest < dump_single.sql

# 方式二:mysql 客户端内 source 命令(交互排查用)
mysql> source /backup/dump_single.sql;

source< 重定向本质相同:mysql 客户端逐行读文件、逐条执行 SQL。单线程、单连接——这是 mysqldump 方案导入慢的根本原因,无论源端机器多少核,导入速度都卡在这一个连接上。

导入期间目标库在干什么:每条 INSERT 要经过 SQL 解析 → 权限检查 → binlog 写入(sync_binlog=1 时每次提交刷盘)→ InnoDB redo 写入(innodb_flush_log_at_trx_commit=1 时每次提交刷盘)→ 数据页修改。一批 INSERT 组成一个事务,每个事务两次 fsync,机械盘上一次 fsync 约 10ms,一秒最多扛几十个事务——导入速度天花板由此决定。

3.5 导入调优:三板斧

生产上用 mysqldump 导大表,默认参数导入慢到怀疑人生。三板斧下来能快一个数量级:

1
2
3
4
5
6
7
8
9
10
11
12
# 第一板斧:关 binlog(目标库导入期间不需要记录 binlog)
mysql --init-command="SET SESSION sql_log_bin=0" -h*** sbtest < dump_single.sql

# 第二板斧:临时调弱刷盘(双1 -> 双0,导完必须改回来)
mysql -h*** -e "SET GLOBAL innodb_flush_log_at_trx_commit=2; SET GLOBAL sync_binlog=0;"
# ... 导入 ...
mysql -h*** -e "SET GLOBAL innodb_flush_log_at_trx_commit=1; SET GLOBAL sync_binlog=1;"

# 第三板斧:导入期间关闭唯一性检查和外键检查(写入产物头)
-- dump 文件开头会自动包含:
-- SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
-- SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;

第三板斧 mysqldump 产物默认自带,前两板斧需要手动。调优实测数据(同机同数据集):

导入方式配置耗时相对默认
默认双 1 + 写 binlog529s基准
调参三板斧flush=2 + sync_binlog=0 + sql_log_bin=0333s快 37%

关掉 binlog 双写和两次 fsync,导入时间砍掉三分之一还多——每个事务省掉的两次刷盘,2000 万行攒下来就是几分钟。

三板斧只是入门,生产上完整的导入参数清单(兼容性防错 3 项 + 性能加速 12 项,含 innodb_doublewrite=OFFinnodb_change_buffer_max_size=50unique_checks=0 等)见 5.2 节。

4. 实验评测:压测下的真实表现

实验设计(详见第一篇):sysbench 2000 万行数据集,迁移全程对源库保持 8 线程 oltp_read_write 压测,对比基线 TPS/P95。

4.1 三套架构结果总表

架构组导出源导出耗时导入耗时(默认)导入后校验
单节点single 本机60s529s20/20 表行数一致
主从master 主库66s458s20/20 表行数一致
读写分离ro 只读节点60s459s20/20 表行数一致

先看大数字:导出 60 秒,导入 529 秒——导入是导出的 8 倍耗时。全流程(导出 60s + 压缩 43s + 导入 529s)约 10.5 分钟迁完 2000 万行,其中导入占了 84%。这就是 mysqldump 方案”导出不难受、导入等半年”的量化呈现。

三组导入耗时差异(458~529s)主要来自实验期间宿主机负载波动(同机跑着 6 个 MySQL 实例),量级上可视为一致。

4.2 迁移期间源库性能退化分析

压测伴随导出的完整数据:

架构组基线 TPS / P95导出期间 TPS / P95变化
单节点256.64 / 25.74ms288.36 / 25.28msTPS +12%
主从(从主库导)259.92 / 30.26ms269.94 / 30.26msTPS +4%
读写分离(从ro导)260.69 / 30.81ms286.25 / 25.28msTPS +10%,P95 -18%

反直觉:导出期间 TPS 反而更高? 先说结论:这是本实验数据集规模下的特例,不具备普遍性,但原理值得说透。

本实验数据文件 4.2GB ≈ innodb_buffer_pool_size(4G)。压测的热点行随机分布全表,基线压测时部分读请求要穿透到磁盘。而 mysqldump 顺序全表扫描把所有数据页都灌进了 buffer pool——相当于免费做了一次全量预热,压测的随机读从”部分命中”变成”全部命中”,抵消甚至反超了导出本身的 IO 开销。

当数据量远大于 buffer pool 时,结论会反转:全表扫描会持续挤掉热页(buffer pool 污染),业务命中率下降,TPS 退化、P95 上升——这才是生产库(数据量通常是内存的几倍到几十倍)导出时的常态。本实验的”反常数据”恰好从边界条件验证了原理:导出对源库的冲击大小,取决于数据量与 buffer pool 的比例

真实生产借鉴:数据量小于内存时放心导;数据量大时,要么选业务低峰,要么从从库/只读节点导(见 4.4)。

4.3 主从架构的复制延迟

主从组(从主库导出)期间,从库 Seconds_Behind_Source 采样(每 5 秒)全程为 0。

原因:mysqldump 是普通 SELECT,走 MVCC 快照读,不产生 binlog、不锁写;从库的 IO/SQL 线程只回放压测的正常写流量,导出对复制链路是”透明”的。低写入压力下,从主库逻辑导出不会引起复制延迟——但注意这是 8 线程压测(写入温和)的结果;高写入压力 + 大事务场景,从库单线程回放本身就会延迟,与导出无关。

4.4 导出节点怎么选:主库、slave 还是 ro

读写分离组:压测打 rw(写节点),导出走 ro(只读节点),与主从组(压测和导出都打主库)对比:

对比项主从组(主库导出)读写分离组(ro导出)结论
写节点压测 P9530.26ms25.28ms从 ro 导出,主库延迟低 17%
写节点压测 TPS269.94286.25从 ro 导出,主库 TPS 高 6%
导出节点自身复制延迟从库 0sro 最高 12s代价来了

从 ro 导出确实给主库减了压(P95 从 30.26ms 降到 25.28ms)。但天下没有免费的午餐:ro 节点一边要回放主库 binlog(SQL 线程),一边要服务 mysqldump 的全表扫描,两者抢 IO/CPU,ro 的复制延迟最高冲到 12 秒(243 次采样为 0s,约 40 次在 1~12s 之间)。

这里有个生产上更值得关注的架构场景:主从架构下,从 slave 导出

在 m-s 架构里,如果 slave 是纯备库(不接任何业务读流量),它就是为这种时刻准备的:主库全力服务业务写入,slave 只做复制回放——此时把导出任务打到 slave 上,是最优解

  • 主库零干扰:导出的全表扫描、网络出口都不碰主库(实测同构数据:主库 P95 -17%、TPS +6%)
  • slave 自身延迟上升完全无害:它不服务读,延迟 0s 还是 12s 业务都无感知,导出完延迟自然归零
  • slave 上的数据经由复制获得,与主库逻辑一致,导出产物与从主库导出等价(位点注意事项见 5.8)

注意本实验读写分离组的 ro 在实验中恰好就处于”只回放复制 + 被导出、不服务读”的状态——压测流量全部打在 rw 上——所以上表 ro 的实测数据,可以直接作为”从纯备 slave 导出”的性能参照。

完整版决策树(导出节点选择):

架构形态推荐导出节点理由
单节点本机(低峰窗口)没得选,控制导出对业务的冲击
主从,slave 纯备库slave最优解:主库零干扰,slave 延迟上升无害
主从,slave 承接读流量主库(低峰)或临时加备库slave 延迟抖动会让读到旧数据
读写分离,ro 服务读视延迟容忍度定ro 导出保护了主库(P95 -17%),但 ro 延迟冲到 12s,业务读到旧数据是否可接受要单独评估
读写分离,ro 有富余/专用导出副本ro/专用副本兼得主库保护和数据一致

5. 会遇到的问题与解决方案

5.1 导入速度慢的根因

实测数据(4.2GB / 2000 万行,单节点组):

环节耗时占比
导出(源库)60s10%
压缩(pigz)43s7%
导入(目标库默认配置)529s84%
导入(调参三板斧后)333s-37%

根因三层:

  1. 单线程:一个连接串行执行所有 INSERT,无法利用多核
  2. binlog 双写:目标库默认把导入的每个事务写进 binlog,纯浪费(导入数据不需要复制出去)
  3. 双 1 刷盘:每个事务两次 fsync

调参三板斧(3.5 节)能砍 37%,但单线程的天花板还在——这是工具的结构性限制,只能靠第三篇的多线程方案突破。

5.2 生产级导入参数清单:先防错,再提速

3.5 节的三板斧是最核心的三件,生产上完整的导入参数配置分两类:兼容性参数(防中途报错)性能参数(提速)。下面这份清单来自生产实践,导入前逐项核对。

兼容性三参数(防导入中途报错)

参数临时值防什么坑
innodb_large_prefixON老版本目标库上超长索引前缀(>767 字节)直接建不出来;8.0 已默认开启且废弃,5.7 及以下目标库要注意
txsql_json_full_precisionON(腾讯云 TXSQL 内核参数)JSON 列全精度存储:源库 JSON 字段含高精度小数时,目标库不开启会精度丢失
innodb_strict_modeOFF关闭 InnoDB 严格模式:行大小超限等在严格模式下直接报错中断,宽松模式降级为警告,先导进去再治理

性能加速:全局参数(导入完必须恢复)

参数临时值提速原理
innodb_flush_log_at_trx_commit0redo 每秒刷一次(比三板斧里的 2 更激进:断电最多丢 1 秒数据,全新目标库无所谓)
innodb_doublewriteOFF关闭双写缓冲:防”半页写”损坏的机制导入期间不需要(坏了重导即可),省一遍顺序写盘
innodb_change_buffer_max_size50change buffer 拉到 50%:大批量 INSERT 对二级索引的变更先缓冲后合并,带二级索引的大表提速明显
innodb_stats_auto_recalcOFF关闭统计信息自动重算:导入期间别让频繁的隐式 ANALYZE 添乱
max_allowed_packet1G与导出端批次匹配,防 “Got a packet bigger than” 报错(详见 5.4)
net_buffer_length1M与导出端批次对齐
tmp_table_size / max_heap_table_size256M减少导入过程中的临时表落盘
slow_query_logOFF大批量 INSERT 会被记成慢查询,慢日志既膨胀又有写入开销,导入期间直接关掉

性能加速:会话级参数(只对导入连接生效,用完即走)

参数提速原理
unique_checks0跳过唯一性检查:InnoDB 信任导入数据无重复,省掉每行的唯一索引校验
foreign_key_checks0跳过外键检查:有依赖关系的表乱序导入不报错
sql_log_bin0导入连接不写 binlog(mysqldump 产物头部自动带,--init-command 显式保证)

三点提醒:

  1. 全局参数导入完成后逐项恢复原值(双 1、双写、change buffer、统计信息、慢日志),这是生产操作的高压线——忘了恢复等于把数据库裸奔上线
  2. unique_checks=0 的前提是数据来自一致性快照、信任源库无脏数据;导入完成后建议对关键唯一索引做一次有效性抽查
  3. 这份清单和三板斧不冲突:三板斧是其中的核心三件(sql_log_bin + 刷盘 + 检查关闭),清单是完整版

5.3 长事务与 undo 膨胀

--single-transaction 导出 4GB 数据需要数分钟,快照期间业务的 DELETE/UPDATE 产生的旧版本行不能被 purge 回收,undo 表空间持续膨胀。如果源库写入压力大,导出前要确认 undo 空间余量。极端情况下(长导出 + 高写入),undo 能膨胀到和数据量同量级。

5.4 max_allowed_packet 陷阱

导出端和导入端的 max_allowed_packet 不一致时:源端按自己的 net_buffer_length 生成了大 INSERT 批次,目标端 max_allowed_packet 更小,导入直接报错:

1
ERROR 1153 (08S01) at line N: Got a packet bigger than 'max_allowed_packet' bytes

解决:导入前确认目标端 max_allowed_packet >= net_buffer_length(导出端),或导出时用 --net-buffer-length=64K 调小批次。

5.5 GTID 环境的 –set-gtid-purged 陷阱

源库开启 GTID 时,mysqldump 默认会在产物头部加 SET @@GLOBAL.gtid_purged='uuid:1-N'--set-gtid-purged=ON),这个默认行为是坑还是宝,取决于目标库的状态。两种方案:

方案 A:目标库已有数据/GTID 记录 → --set-gtid-purged=OFF

目标库已有自己的 GTID 执行历史时,导入带 SET @@GLOBAL.gtid_purged 的产物会直接报错 @@SESSION.GTID_NEXT cannot be changed,或污染目标库的 GTID 集合导致后续搭复制出诡异问题。这种场景导出时显式加 --set-gtid-purged=OFF,增量追赶用自己的位点记录单独管理。

方案 B:目标库是干净空白的新实例 → 保留默认(推荐生产使用)

如果目标库是一个全新空白的实例(gtid_executed 为空),建议保留 --set-gtid-purged=ON 的默认行为,彻底忽略目标库、完全以源库的 GTID 为主:

1
2
3
4
5
# 导出(不加 --set-gtid-purged=OFF,保留 GTID 信息)
mysqldump --single-transaction --source-data=2 --databases sbtest ... > dump.sql

# 导入后,目标库的 gtid_executed 与源库导出时刻完全一致
mysql> SELECT @@GLOBAL.gtid_executed;   -- uuid:1-N,与源库相同

生产环境里方案 B 通常更好,因为导入后的目标库和源库在 GTID 维度上完全相同

  1. 全量导入的这批数据,在目标库上”看起来就像源库自己执行过的一样”——GTID 历史无缝继承
  2. 后续增量追赶(割接阶段)直接用 GTID 复制接上源库:CHANGE REPLICATION SOURCE TO ... SOURCE_AUTO_POSITION=1,源库发来的事务里凡是目标库 GTID 集合中已有的,自动幂等跳过,不会重复执行
  3. 迁移完成后如果要把目标库升格为源库的从库(做双活校验或回切兜底),GTID 体系天然成立,不需要任何位点换算

方案 B 的铁律:目标库必须是 RESET MASTER 级别的干净实例(生产迁移的目标端本来就是新机器/新实例,天然满足)。目标库有一丝自己的 GTID 历史,方案 B 就退化成报错现场。

一句话总结:目标库空白,让源库 GTID 全量接管(方案 B,生产首选);目标库有历史,关掉 GTID 注入各管各的(方案 A)。本系列实验用的是方案 A(实验目标库被多组实验反复导入,GTID 历史不干净)。

5.6 字符集与 collation 兼容

字符集的坑分两层:

第一层:collation 不兼容。源库 utf8mb4_0900_ai_ci(8.0 默认)导到 5.7 目标库会报错(5.7 没有这个 collation)。跨大版本向下迁移要显式指定 --default-character-set,或者接受 collation 降级。

第二层:源和目标字符集不同(比如源是 utf8mb3、目标是 utf8mb4)。

utf8mb3 是 utf8mb4 的子集,字节层面天然兼容,看起来”升格”很安全——但坑藏在导出/导入连接的字符集转换层:连接字符集和数据字符集不一致时,server 会按连接字符集对字符串做一次转码再吐给客户端,产物文件里的字节已经不是源库里的原始字节了;这个转换一旦遇到不可映射字符,轻则乱码、重则报错中断。

解法:导出时让字符串列走 _binary 语义,按原始字节导出,绕过连接层转码

1
2
3
# --default-character-set=binary:导出连接用二进制字符集,
# 字符串数据按源库里的原始字节写入产物,不做任何转码
mysqldump --default-character-set=binary --single-transaction ... > dump.sql

产物里的 INSERT 语句是字节的”原样搬运”,导入到 utf8mb4 目标库时按目标列字符集直接落盘——utf8mb3 的字节在 utf8mb4 下全部合法且含义不变,数据零损耗。对含 BLOB 的表再加 --hex-blob,二进制列以十六进制字面量(0x...)导出,彻底杜绝编码歧义。

直白记法:字符集不同先别慌,binary 导出保平安——字节搬运比字符转换可靠,只要”源字符集是目标字符集的子集”(utf8mb3→utf8mb4、latin1→utf8mb4 前提是确认数据确实是纯 latin1),这条都成立。反过来(utf8mb4→utf8mb3)是降格,4 字节 emoji 无处安放,先清洗数据再迁。

5.7 导出对源库的冲击

实测压测数据(完整分析见 4.2 节):

  • 本实验数据量(4.2GB)≈ buffer pool(4G)时,导出甚至因”全量预热”让压测 TPS 不降反升(+4%~12%)
  • 数据量远超 buffer pool 时结论反转:全表扫描持续挤掉热页(buffer pool 污染),业务 TPS 退化、P95 上升——这是生产库导出的常态
  • 退化三大来源:buffer pool 污染、导出连接占用网络带宽、--single-transaction 长事务撑大 purge 滞后(undo 膨胀,见 5.3)

5.8 从库/只读节点导出的位点问题

从只读副本导出(给主库减压)时:副本通常没开 binlog,--source-data 的 file+pos 位点拿不到——这正好是 GTID 位点的主场(2.4 节):GTID 环境下导出后立即在副本上执行 SHOW REPLICA STATUSExecuted_Gtid_Set(副本 SQL 线程已回放的事务集合,即快照对应的精确位点),增量追赶直接 SOURCE_AUTO_POSITION=1,比手工换算 file/pos 可靠得多。唯一要注意:这个集合比源库当前位点旧,增量追赶时确认源库 binlog 保留时长(或 GTID 保留)覆盖这个差值。

6. 适用数据量级结论

生产经验值:mysqldump 的适用范围在 300GB 以内——生产机器规格高、网络快,配合低峰窗口和三板斧调参,300GB 以内的迁移完全能撑住(按本实验单机折算全流程约 12.5 小时,生产高配机器可显著缩短)。

需要客观看待的边界:

关注点说明
耗时随数据量线性增长单线程导入没有并行加速,数据量翻倍时间翻倍,300GB 是”窗口能等”的经验上限
迁移窗口要求有明确窗口要求(比如必须 4 小时内完成)的,300GB 以内也要先实测折算
超出 300GB不勉强,直接看第三篇 mydumper/myloader

实验数据参考(2000 万行 / 4.2GB:导出 60s + 压缩 43s + 导入 529s ≈ 10.5 分钟全流程,调参后 6.5 分钟)。

直白结论:300GB 以内、迁移窗口不敏感(可以慢慢导)的场景,mysqldump 依然是生产上最省心的选择;数据量再大或者有明确窗口要求的,看第三篇 mydumper/myloader

7. 总结

  1. mysqldump 导出环节其实不慢(60 秒导完 2000 万行),瓶颈全在导入(529 秒,占全流程 84%):单线程 + 双 1 刷盘 + binlog 双写是三座大山,三板斧调参砍掉 37%
  2. 导出对源库的冲击取决于数据量与 buffer pool 的比例:数据量小于内存时甚至有”预热红利”(TPS 不降反升);数据量远超内存时 buffer pool 污染会让业务明显退化——生产库属于后者,导出要选低峰或走从库
  3. 导出节点选择是个架构问题(4.4 决策树):主从架构的 slave 若是纯备库,从 slave 导出是最优解(主库零干扰、延迟上升无害);副本服务读流量时才需要权衡——从 ro 导出保护主库(P95 低 17%)但副本复制延迟冲到 12 秒
  4. 导入前参数分两步配:兼容性三参数(innodb_large_prefixtxsql_json_full_precisioninnodb_strict_mode)防中途报错,性能加速 12 项(关双写、拉 change buffer、跳检查等)提速——导入完成后全局参数必须逐项恢复;GTID、字符集、max_allowed_packet、DROP TABLE 语义,四个坑一个都不能踩
  5. 量级结论(生产经验):300GB 以内放心用,超出后果断换 mydumper/myloader

下一篇用多线程的 mydumper/myloader 重新跑同样的实验,看看多线程能带来多大提升:

下一篇:MySQL 数据迁移(三)mydumper/myloader 多线程迁移


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文由作者按照 CC BY 4.0 进行授权