
1. 先想清楚这次导出的数据要给谁用、用在哪MySQL 导出数据这件事看起来就是个把数据弄出来的动作但在实际工作中我见过太多人在第一步就栽了跟头——不是命令不会敲而是根本没想清楚导出的数据最终要流向哪里。不同去向对应完全不同的导出方案选错了工具后面全是坑。先说最常见的三种场景。第一种是备份和迁移。把生产库的数据导出来要么是换服务器要么是升级版本要么是给测试环境造一份数据。这种场景的目标端是另外一个 MySQL 实例要求的不是 Excel 表格而是完整的表结构、索引、触发器、存储过程甚至包括自增 ID 的当前值。这时候你如果图省事用图形化工具导了个 CSV回头导入的时候就会发现外键关系全乱套、字符集对不上、连自增主键的起始值都丢了。第二种是跨系统数据交换。客户或业务方说给我一份订单数据我要做分析或者公司内部要从 MySQL 往数仓、往报表系统里灌数据。这种场景的目标端是 Excel、CSV、第三方平台核心诉求是人能看懂格式干净列名清晰表结构、索引这些东西反而无关紧要。第三种是数据归档和清理。把几年前的流水表导出来存到冷存储里然后从生产库删掉。这种场景的目标端是文件压缩包可能永远都不会再导回来了要求的是体积小、可校验、存放安全。我的建议是动手之前先回答三个问题目标端是什么数据量大概多少对表结构和约束有没有要求这三个问题的答案直接决定了你该用 mysqldump、SELECT INTO OUTFILE 还是 Navicat 导出向导。别嫌麻烦我在生产环境里处理过不少事故很多都是因为先导出来再说这个心态导致的——导出来是小事导完之后数据没法用才是大事。另外还要考虑数据量和超时问题。哪怕只是几万行的表如果用客户端工具一次性导出内存占用和网络传输都可能把笔记本搞到卡死反过来几百行的配置表根本不需要上 mysqldump一条 SELECT 导出 CSV 就够用了。工具的选择一定要匹配量级这点后面每个章节都会展开讲。2. mysqldump 命令行备份场景最省心的主力方案2.1 基础命令与核心参数mysqldump 是 MySQL 自带的逻辑导出工具也是我在备份迁移场景里最依赖的方案。它的本质是执行一系列查询把数据转换成 INSERT 语句或 CSV 文本写到文件里。它唯一的缺点是导出的是逻辑数据而不是物理文件导入时需要通过 SQL 回放速度比物理文件拷贝慢优点是跨版本、跨平台兼容性好还能精确控制导出粒度。我用得最多的基础命令长这样mysqldump -h 127.0.0.1 -P 3306 -u root -p \ --single-transaction \ --set-gtid-purgedOFF \ --default-character-setutf8mb4 \ --routines --triggers \ mydb mydb_$(date %F).sql逐项拆开解释一下--single-transaction针对 InnoDB 表开启一个一致性快照导出期间不锁表。这个参数在在线导出时几乎是必须的没有它的话MyISAM 表会被全局锁定业务写入直接卡住。--set-gtid-purgedOFF如果你的库启用了 GTID 复制导出文件里默认会带上一行 SET GTID_PURGED 语句。导入到其他实例时这行语句有时候会触发GTID_PURGED can only be set when GTID_EXECUTED is empty的报错。普通导出场景直接关闭它最省心。--default-character-setutf8mb4明确指定导出字符集。如果库本身用的 utf8mb4这行参数保证导出来 SQL 文件里的中文不乱码如果库是老的 utf8导入端也要保持一致的设置。--routines --triggers连带存储过程和触发器一起导出。很多人漏掉这两个参数导致迁移之后应用调用存储过程直接报错。比如要导出单张表加个表名即可mysqldump -u root -p mydb orders orders.sql多张表就并列表名mysqldump -u root -p mydb orders order_items users business_tables.sql只导出结构不导出数据加--no-data只导数据不导结构加--no-create-info。这两个参数在搭建测试环境、同步表结构变更的场景里特别有用。2.2 大表备份如何控制导出内容生产环境的表往往是千万级起步mysqldump 默认会导出全表数据。偶尔业务方只需要某个时间段的数据做排查就没有必要导全表。这时候可以用--where参数做条件过滤mysqldump -u root -p mydb orders \ --wherecreated_at 2024-01-01 AND created_at 2024-02-01 \ orders_202401.sql需要注意--where的语法直接拼接到 SELECT 语句里所以字段名必须是表里真实存在的列字符串条件要自己处理转义。另外这种方式只对 InnoDB 单表导出有效果如果你在导出多个表时用--where它会作用于所有表——这是我踩过的坑之一务必留意。还有一个参数--max-allowed-packet它控制的是 mysqldump 客户端和 MySQL 服务端通信时的最大数据包大小。如果表中某一行有较大的 TEXT、BLOB 字段导出时容易触发 Packet Too Large 报错。遇到这种情况在 mysqldump 命令里加上mysqldump --max-allowed-packet256M -u root -p mydb mydb.sql同时在导入端的 my.cnf 里也要把max_allowed_packet调大否则导出没问题导入时照样本应该报错。2.3 导出文件如何压缩与分卷一整库导出的 SQL 文件动辄几个 GB直接放在磁盘里不仅占空间传输也慢。我习惯导出后用 gzip 压缩mysqldump -u root -p mydb | gzip mydb_$(date %F).sql.gz管道压缩的好处是省掉一个中间文件磁盘 IO 压力更小。恢复时直接解压导入gunzip mydb_$(date %F).sql.gz | mysql -u root -p target_db如果你的文件大到单次传输有困难可以用 split 分卷。用 Linux 下的split命令按大小切割split -b 2G -d -a 3 mydb.sql mydb_part_生成mydb_part_000、mydb_part_001这样的分卷文件传输到目标机器后用cat合并回来再导入。-d让分卷用数字后缀-a 3指后缀长度这两个参数建议加上否则默认的字母后缀在超过 26 个分卷后会变得混乱。3. 导出 CSV 和 Excel业务交付场景的细节与坑3.1 SELECT INTO OUTFILE 的用法与权限限制当目标端是数据分析师、Excel 重度用户或者第三方系统时导出 CSV 是最实在的方式。MySQL 原生提供了 SELECT INTO OUTFILE 语句性能比用客户端工具一条条查出来再写文件强得多因为数据是服务端直接落盘不经过网络。SELECT order_id, user_id, total_amount, created_at FROM orders WHERE created_at 2024-01-01 INTO OUTFILE /var/lib/mysql-files/orders_202401.csv FIELDS TERMINATED BY , OPTIONALLY ENCLOSED BY ESCAPED BY \\ LINES TERMINATED BY \n;这里有几个关键点。第一INTO OUTFILE有个安全限制MySQL 的secure_file_priv变量指定了导出文件可写入的目录。如果该变量为空所有目录都不能写如果是/var/lib/mysql-files/那么文件只能写到这个目录下。查询方式SHOW VARIABLES LIKE secure_file_priv;网上很多人报错 The MySQL server is running with the --secure-file-priv option so it cannot execute this statement就是没看这个配置。解决办法是把导出路径改到允许的目录或者在 my.cnf 里设置secure_file_priv/来放开限制生产环境不建议安全风险高。我把这个问题放在前面讲因为它是最容易卡住新手的第一关。第二FIELDS TERMINATED BY定义字段分隔符。默认是制表符但 Excel 打开制表符分隔的文件经常会错位还是建议显式用逗号。OPTIONALLY ENCLOSED BY 表示字符串类型的字段用双引号包裹防止字段值里包含逗号导致列错位——这个参数非常重要订单备注里出现你好世界这类含逗号的文本太常见了。第三如果字段为 NULL导出默认是\N而不是空字符串。数据分析师看到一堆\N会懵可以在 SELECT 时用IFNULL(字段, )处理从源头把 NULL 转成空字符串。3.2 字符集与 Excel 打开乱码的根治方案我敢说导出 CSV 乱码是遇到频率最高的问题没有之一。原因在于 MySQL 的 CHARACTER SET、连接字符集、导出文件编码、Excel 默认打开编码这四个环节只要有一个不一致中文就变乱码。先说结论目前国内 Windows 环境最稳妥的排查顺序是导出的 SQL 语句执行前设置SET NAMES utf8mb4;确定 MySQL 服务端、表、字段的字符集为 utf8mb4导出后用file命令检查文件编码Linux 下用支持编码选择的编辑器或导入工具打开CSV 文件本身不记录编码信息Excel 在 Windows 中文环境默认以 ANSIGBK编码打开 UTF-8 文件于是中文全变成锟斤拷。绕开这个问题的经典办法是在文件开头加 BOM。BOM 是一串可见字符标记EF BB BFExcel 看到 BOM 就能正确识别为 UTF-8。在用INTO OUTFILE语句时无法直接加 BOM我通常采取两步# 先导出数据 mysql -u root -p -e SET NAMES utf8mb4; SELECT ... INTO OUTFILE /var/lib/mysql-files/orders.csv ... # 再用 sed 在文件头插入 BOM sed -i 1s/^/\xef\xbb\xbf/ /var/lib/mysql-files/orders.csv如果你用 Python 或 Shell 脚本导出 CSV那就简单些直接在写文件的时候写入utf-8-sig编码Python 的utf-8-sig就是带 BOM 的 UTF-8。这些年我帮同事处理乱码问题最多的是Excel 打开乱码最小的工作量就是加 BOM一定先试这个别急着重导数据。3.3 图形化工具Navicat 和 DBeaver 的导出向导非技术人员或者需求简单、只导出几万行数据时用 Navicat 这类图形化工具最直观。Navicat 的操作路径右键点击目标表 → 导出向导 → 选择格式Excel、CSV、SQL 等→ 选择字段 → 设置选项 → 开始导出。它有个我很喜欢的细节导出 Excel 时可以直接生成.xlsx多个表还能导出到同一个工作簿的不同 sheet非常方便做报表交付。但 Navicat 有个隐藏问题它是客户端工具本质上是把数据一条条从服务端拉下来再写入本地文件。对于几万行的表没问题几百万行的表就非常吃力了不仅慢还容易把本机的内存吃满。我实测过千万级表用 Navicat 导出 Excel跑了十分钟没结束最后直接放弃转用命令行方案。DBeaver 是开源免费的选择逻辑类似而且它可以作为数据库客户端连接 MySQL 来执行查询。导出时注意在查询结果面板选择导出数据选 CSV 格式时把使用 BOM的选项打开避免 Excel 乱码。DBeaver 的还好一点它有一个导出 SQL 的选项可以生成类似 mysqldump 的 INSERT 语句适合小表做轻量迁移。4. 大数据量导出的性能陷阱与提速手段4.1 先搞清楚导出慢在哪很多人遇到大数据量导出第一反应是加服务器配置但 MySQL 导出慢通常不是 CPU 不行瓶颈一般在这三处单线程查询mysqldump 本质上是一个大查询全表扫出来的一个线程从头扫到尾耗时与表行数和行大小线性正比。磁盘 IO导出数据要从 InnoDB buffer pool 里把数据块刷出来遇到冷数据还要读磁盘导出后写入文件又产生一次磁盘写。IO 忙时导出肉眼可见地变慢。网络传输用 Navicat、客户端工具导出时数据要经过 MySQL 客户端协议传输到本地网络带宽就是天花板。解决思路不是换更强的机器而是想办法绕过这些瓶颈或者并行化。4.2 分批次导出避免单一长事务如果一个大表需要导出给业务方且没有一次性全量的硬性要求最简单有效的办法是分段导出。比如按主键 ID 范围分片mysqldump -u root -p mydb orders \ --whereid 1 AND id 1000000 orders_part1.sql也可以按时间分片。我在实际项目中常用 Python 脚本配合WHERE id BETWEEN ? AND ?循环导出每片控制在 50 万到 100 万行之间这样每个导出任务都很快结束中途出大问题比如网络断了也不会全部重来。分片时注意条件字段上必须有索引否则每个分片的查询都会走全表扫描比不分片还要慢得多。用主键 ID 分片是最可靠的选择。4.3 mysqldump 的并行导出思路mysqldump 的历史版本是单线程的但 MySQL 8.0 引入了--innodb-optimize-keys之类的优化参数还有一种更实用的并行方案是把要导出的表按逻辑分组每个组一个 mysqldump 进程并行跑。比如一个库里有订单、用户、商品三大核心表各自独立那么可以开三个终端窗口同时导出# 终端1 mysqldump -u root -p mydb orders orders.sql # 终端2 mysqldump -u root -p mydb users users.sql # 终端3 mysqldump -u root -p mydb products products.sql并行导出在 IO 充裕的情况下能接近线性提升速度但要注意控制并行度——并发太高会导致大量表被同时锁如果没开--single-transaction或者 IO 负载飙升反而影响生产业务。一般 2 到 3 个并发是安全值。4.4 导出即压缩与传输优化导出大 SQL 文件直接传输网络带宽就是瓶颈。我最常用的组合是导出时边管道边压缩mysqldump --single-transaction -u root -p mydb | gzip -1 mydb_$(date %F).sql.gzgzip -1是压缩级别 1压缩率较低但速度快适合数据量大、机器性能一般的场景要求体积更小可以用gzip -9但耗时明显增加。实测下来默认的-6是性价比最好的选择大部分情况我直接不加参数。此外导出和传输尽量安排在业务低峰期。我一般定在凌晨 2 点到 5 点之间跑定时任务配合nice -n 19降低进程优先级nice -n 19 mysqldump --single-transaction -u root -p mydb | gzip mydb.sql.gznice -n 19让导出进程在 CPU 调度里排在最后这样就算导出任务占用资源生产连接也不会被明显拖慢。5. 导出过程中的锁表、一致性以及常见报错5.1 为什么导出会把业务卡死锁机制详解先讲个真实事故。有一次我在凌晨用 mysqldump 备份一个核心库备份完早上业务方就反馈卡顿、写入延迟很高。查了半天发现备份任务没有加--single-transaction而库里有几张表是 MyISAM 引擎备份过程把整张表锁住了其他写入一直阻塞到备份结束。这个事故的原理很简单MyISAM 表只有表级锁mysqldump 在导 MyISAM 表时会执行 LOCK TABLES期间所有对该表的读写都被挂起。而 InnoDB 支持行级锁和 MVCC--single-transaction让 mysqldump 基于一个一致性快照读取数据读写互不阻塞。所以所有生产环境的备份导出命令只要 InnoDB 表居多强烈建议加--single-transaction。遇到 MyISAM 表要么先把它改成 InnoDB要么忍受短暂的锁表窗口选在业务低峰跑。另外导出命令里可以增加--skip-lock-tables避免导 MyISAM 时执行全局 LOCK TABLES——但副作用是 MyISAM 各表之间导出的数据可能不是同一时间点的快照一致性会打折扣。5.2 常见导出报错排查清单日常导出 MySQL 数据时翻来覆去就是那几个报错我把处理过的整理成一张表报错信息根因处理方式Access denied for user xxxhost用户没有相应权限至少需要 SELECT、LOCK TABLES、SHOW VIEW 权限导出存储过程还需要 EVENT、TRIGGER 权限The MySQL server is running with the --secure-file-priv option导出文件路径不在允许目录SHOW VARIABLES LIKE secure_file_priv把文件写到允许目录或调整配置mysqldump: Couldnt execute SHOW TRIGGERS用户缺少 TRIGGER 权限授权GRANT TRIGGER ON db.* TO userhostGot error: 1556/Lost connection单条数据太大或网络超时加大max_allowed_packet或分批导出Table xxx was locked同时有别的会话在操作这张表查看SHOW PROCESSLIST找到锁更长的会话再处理乱码字符集链路不一致统一 utf8mb4导出文件加 BOM针对 Excel排查的思路也和排查普通 SQL 一样先看错误文本再结合当前用户的权限、表引擎、网络状况逐项检查。尤其是权限很多人从开发库导数据用 root到生产环境用普通账号结果各种权限报错第一步应该先SHOW GRANTS FOR userhost;看清楚自己到底有没有权限。5.3 SSL 连接问题对导出的影响有些数据库实例开启了 SSL 要求比如云数据库 RDS 默认开启 SSL这时用 mysql 客户端导出会遇到类似 SSL connection error: unknown error number 的报错。解决办法很简单在连接参数里加上--ssl-modeDISABLED或者--ssl-modePREFERRED来适配mysqldump --ssl-modePREFERRED -h rm-xxxx.mysql.rds.aliyuncs.com -u user -p mydb mydb.sql还有一种情况是本地自建 MySQL 开启了 require_secure_transport客户端连上就强制要求加密。mysqldump 对 SSL 的支持没问题缺的是 CA 证书路径配置。参数mysqldump --ssl-ca/path/to/ca.pem --ssl-cert/path/to/client-cert.pem --ssl-key/path/to/client-key.pem ...遇到 SSL 报错先别慌用mysql -h ... -u ... -p -e SELECT 1;测试基础连接确认能连上再套 mysqldump。6. 导出完成后要做的三件事校验、导入与收尾6.1 用行数核对代替看起来应该没问题导出完成不等于任务结束。数据库里有一条铁律没有经过验证的数据默认就是坏的。我这个习惯是被一次事故逼出来的——有一次给客户迁移数据导出后想当然觉得文件没问题结果导入时发现有几张表的数据行数对不上排查发现是导出过程中某个分片任务失败但脚本没有报错退出。所以现在无论导出什么我都会做三步校验。第一步源库和目标端或导出文件的行数对齐# 源库行数 mysql -u root -p -e SELECT COUNT(*) FROM mydb.orders; # 导入后目标库行数 mysql -u root -p -e SELECT COUNT(*) FROM target_db.orders;如果行数一致说明大概率没问题如果不一致就需要用分组聚合或抽样比对来定位差异。对于亿级大表COUNT(*) 会有一点耗时可以接受相比迁移后出问题的代价小得多。第二步校验关键字段。比如自增主键的最大值是否一致时间字段的最大最小值是否对得上SELECT MAX(id), MIN(created_at), MAX(created_at) FROM orders;第三步抽样对比明细。随机取几十行把源库和目标库的字段拼起来做比对重点看金额、状态、时间这类业务敏感字段。6.2 导入目标库的常见姿势导出的 SQL 文件导入目标库命令很简单mysql -u root -p target_db mydb.sql如果文件是 gzip 压缩的先解压再导入。导入前的关键检查项目标库字符集是否与源库一致特别是表级和库级。目标库的sql_mode是否比源库更严格比如源库允许插入 0000-00-00目标库直接拒绝。目标库有没有同名表。默认情况下SQL 文件里的 CREATE TABLE 如果表已存在会报错可以加--force忽略部分错误继续执行但这很危险容易让数据半途而废。导入大文件时建议在 mysql 命令行加--max-allowed-packet和--compressmysql --max-allowed-packet256M -u root -p target_db mydb.sql导入过程中建议打开 performance_schema 或开启 general log 来观察导入进度但生产环境不建议开 general log避免磁盘被刷爆。更实用的做法是用 source 命令在 mysql 交互环境里导入能实时看到每条 SQL 的执行结果方便定位报错。6.3 清理临时文件和权限收尾导出工作做完后临时文件要及时清理尤其是包含敏感业务数据的 CSV 和 SQL 文件。我见过同事把包含用户手机号的导出文件直接放在服务器 /tmp 目录下忘了删后来被安全扫描抓到很是麻烦。建议的收尾清单按约定的目录存放导出文件文件命名带上日期和数据范围。敏感数据的生产导出文件权限设置成 600。不需要的中间文件立即删除需要保留的明确保留周期。如果用到了临时账号做导出记得收回权限。最后再分享一个小技巧。如果你需要导出的数据是周期性、重复性的任务比如每周给业务方导一份报表别每次手动敲命令把命令写成一个 Shell 脚本配合 crontab 定时执行输出文件名带上日期#!/bin/bash DATE$(date %F) mysqldump --single-transaction -u backup_user -p密码 mydb orders \ --wherecreated_at DATE_SUB(CURDATE(), INTERVAL 7 DAY) \ | gzip /data/export/orders_${DATE}.sql.gz这样既不会忘每次任务统一落盘后续排查也省事。导数据这件事说到底是小事但数据完整性一旦出了问题就是大事把流程固定下来比依赖某一次小心操作可靠得多。