☰
MySQL批量导入导出实战:LOAD DATA INFILE、mysqldump选型与避坑指南
2026/10/22 5:58:41 网站建设 项目流程

做数据库相关工作久了,你会发现一件事:数据量一旦上到几十万行,最耗时间的往往不是业务逻辑,而是数据搬运。有一次接到一个需求,要把三十万行业务流水导入线上库,最开始用程序一条一条 INSERT,跑了快二十分钟还没结束。同事在旁边说了一句,怎么不用 LOAD DATA INFILE?我查了一下,改成 load data infile 之后,同样是这批数据,几秒钟就进去了。从那次之后,凡是涉及 MySQL 批量入库、批量导出的活,我都会先想想三种方法——LOAD DATA INFILE、SELECT INTO OUTFILE、mysqldump——到底该用哪个。这个标题相关的经验,就是我当时踩了一圈坑之后整理出来的。

无论你是做数据分析、系统开发,还是专职 DBA,数据迁移和备份几乎是躲不开的日常操作。小文件用工具拖一下无所谓,但量一大,方式选错了,轻则等上几十分钟,重则把线上库拖垮。这篇文章把三种常用的导入导出方式讲透,包括它们各自的适用边界、参数细节、以及普通文档里不会告诉你的那些坑。顺手记录了一些我自己实际碰到的报错和排查思路,希望能帮你少走点弯路。

1. 先说结论:三种方式分别适合什么场景

1.1 同样是导入导出,差异到底在哪

很多人容易把 LOAD DATA INFILE 和 mysqldump 混为一谈,觉得反正都是“把数据搬进搬出”。其实它们的工作层级完全不同。

LOAD DATA INFILE 是数据库服务端直接读取磁盘上的数据文件,把解析后的行数据批量写入目标表。它的核心优势在于:绕过了一条条 INSERT 语句反复经历 SQL 解析、权限检查、执行计划生成这些环节,用一条 LOAD 语句完成整批数据的写入路径,性能损耗大幅下降。配合 InnoDB 的插入机制,大批量数据进入的速度可以比逐行 INSERT 快几十倍,这是我在实际中测试过的差距,不是理论上的说法。

SELECT INTO OUTFILE 则是把一张表的查询结果直接写到服务端磁盘上,生成一个纯文本文件。它和 LOAD DATA INFILE 正好是一对:一个把文件往库里导,一个把库里的数据往文件里导。两者配合起来,可以构造出非常规整的数据交换格式。

而 mysqldump 是逻辑备份工具,它产生的是 SQL 语句集合,里面包含了建表语句、INSERT 语句,以及可选的各种参数控制信息。它的主要场景是跨环境、跨版本迁移,或者生成逻辑上可读的备份文件。你可以在任何一台 MySQL 上通过执行 SQL 文件把数据还原出来,但它的执行速度天然不如直接文件导入,因为它本质上还是在跑大量 INSERT。

1.2 一张表理清选型思路

在实际确认用哪种方式前,我会先按数据量、场景、权限限制三条线来判断:

方式原理适合场景性能特点主要限制
LOAD DATA INFILE服务端读文件批量写入大量数据快速入库、初始化数据极高,通常比 INSERT 快数十倍需要 FILE 权限,受 secure_file_priv 限制
SELECT INTO OUTFILE查询结果写到服务端文件按需导出明细数据、生成交换文件高,批量一次性写出需要有 FILE 权限,服务端目录可控
mysqldump生成逻辑 SQL 备份跨库迁移、整库备份、结构导出中,导入时以 INSERT 为主数据量大时备份和还原都比较慢

我一般这样选:如果是几万行以上的数据要进库,优先 LOAD DATA INFILE;如果只是把某个结果集交付给数据分析同事,用 SELECT INTO OUTFILE 导出文件最干净;如果目标是备份整个库,或者把库从测试环境同步到生产环境,那无脑 mysqldump 就是最成熟的选择。

2. LOAD DATA INFILE 高效导入的实操细节

2.1 基础语法与让导入真正变快的关键配置

完整的基础用法是这样的:

LOAD DATA INFILE '/data/import/orders.csv' INTO TABLE orders CHARACTER SET utf8mb4 FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\n' IGNORE 1 LINES (order_id, user_id, amount, order_time);

这里有几个容易踩的点。默认情况下,服务端读取文件时会把反斜杠当成转义符,如果你的数据里恰好有反斜杠或者特殊字符,最好显式指定 ESCAPED BY;CSV 中常见的是双引号包字段,所以要用 OPTIONALLY ENCLOSED BY '"' 把引号剥掉。

想让导入速度最大化,除了语法本身,我强烈建议在导入前做两件事:

SET FOREIGN_KEY_CHECKS = 0; SET UNIQUE_CHECKS = 0;

如果有外键和唯一索引,InnoDB 在插入每条记录时都要检查一遍约束,这个开销非常大。临时关掉,导入完再打开,几十万条数据的耗时差距肉眼可见。同理,如果目标表有多个非唯一索引,可以考虑先删索引、导入完成后重建,这种手法在做数据仓库初始化时很常见。

2.2 FIELDS、LINES 子句里的坑与字符集处理

很多人写 LOAD DATA INFILE 时只写了文件路径和表名,剩下的全用默认值,然后就会遇到各种离奇问题。默认的 FIELDS TERMINATED BY 是制表符 \t,默认的 LINES TERMINATED BY 是换行符 \n,默认还会把空字段导入成 NULL。如果你的源文件不是制表符分隔,就必须把 FIELDS 子句写清楚。

字符集也是一个高频坑点。文件是什么编码,就得在 LOAD 语句里指明 CHARACTER SET,否则服务端会按它自己的 character_set_connection 去理解文件字节。之前我导过一份 UTF-8 编码的 CSV,但库里表的默认字符集是 latin1,导入后中文字段全部变成了问号。解决办法很简单:导入时显式加一句 CHARACTER SET utf8mb4,同时确认目标表字段的字符集也是 utf8mb4。

另外要注意文件路径的语义:不带 LOCAL 时,文件路径是相对于 MySQL 服务端的,必须放在 secure_file_priv 限定的目录里;带 LOCAL 时,文件是客户端本地的,通过客户端协议传给服务端。这里有一个性能差异:LOCAL 模式数据要经过客户端到服务端的网络传输,速度会受影响,能用服务端本机文件就尽量别用 LOCAL。

2.3 空值、默认值和类型转换的经典处理

源文件中经常会出现 NULL、空字符串、日期格式不一致等问题。LOAD DATA INFILE 在处理这些情况时有一套自己的规则:文件里的 \N 会被识别为 NULL;如果字段定义为 NOT NULL 且没有默认值,空字符串可能会被转为隐式默认值,这个行为在不同 sql_mode 下不同,很容易让人头大。

我有一次导用户表,文件里的性别字段是空字符串,字段本身没有默认值,结果导入后所有空字符串都变成了 0,业务方拿到数据后一脸懵。后来我学乖了,遇到这种字段,用临时变量接住再转换:

LOAD DATA INFILE '/data/import/users.csv' INTO TABLE users FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\n' IGNORE 1 LINES (user_id, user_name, @gender, created_at) SET gender = IF(@gender = '', 'unknown', @gender);

这样的写法不仅能把空字符串处理成业务想要的值,还能做简单的数据清洗。日期格式不标准时也可以用 STR_TO_DATE 转换,比如:

SET order_time = STR_TO_DATE(@order_time, '%Y/%m/%d %H:%i:%s');

LOAD DATA INFILE 看起来只是导数据,实际上可以当做一个轻量的清洗管道使用,这一招在很多临时数据接入场景下非常有用。

3. SELECT INTO OUTFILE 及配套导出方案

3.1 先弄懂 outfile 的文件权限与服务端路径

SELECT INTO OUTFILE 的语法看起来很容易理解:

SELECT order_id, user_id, amount, order_time FROM orders WHERE order_time >= '2024-01-01' INTO OUTFILE '/data/export/orders_202401.csv' CHARACTER SET utf8mb4 FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' LINES TERMINATED BY '\n';

但实际用起来有几个先决条件会卡得人很难受。第一,MySQL 8.0 之后 secure_file_priv 默认是 NULL,也就是说服务端文件导入导出功能默认关闭。你必须先在 my.cnf 里配置 secure_file_priv 指向一个具体目录,然后重启服务。第二,执行用户需要 FILE 权限。第三,目标文件不能已存在,每次导出前需要先清理上一次的文件,否则会直接报错。

还有一个容易忽略的点:这个文件是写在 MySQL 服务端所在的主机上,不是你执行 SQL 的客户端机器。如果你通过远程工具执行这条语句,并不会把文件下载到你本地。跨机器使用场景下,一般需要先导出到服务端,然后在服务端上再拷贝走,或者直接让服务端提供文件下载通道。

3.2 导出格式设计与回导入的闭环配合

很多人导文件时不在意格式,随便定个分隔符,结果下游程序解析得欲仙欲死。我的习惯是:先想清楚这份文件将来要卖给谁。如果只是给人临时看一眼,逗号分隔就好了;如果是程序自动化消费,最好是 \t 分隔或自定义分隔符,避开内容里的逗号干扰。

把导出的文件重新导回去是很常见的闭环操作。为了让回导过程不折腾,导出和导入的 FIELDS 子句要保持一致。我通常固定一套组合:

FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' ESCAPED BY '"' LINES TERMINATED BY '\n'

导出时用这一套,导入时也用这一套。特别是 ESCAPED BY,如果没有指定,默认是反斜杠,数据里如果有引号或反斜杠就会被加一层转义,再导回时会引入脏数据。我吃过这个亏,导出的字段值里带了双引号,导入时因为转义规则不一致,解析错位,整批数据错行。后来我统一了导出和导入的转义符,问题才彻底解决。

3.3 本地导出时的备选方案

并不是所有场景都能用 SELECT INTO OUTFILE。有时候服务端目录权限受限,或者只是想临时拿一部分数据看看,直接用 mysql 客户端把查询结果重定向到本地文件更方便:

mysql -h 127.0.0.1 -uroot -p database_name -N -e "SELECT order_id, user_id, amount FROM orders LIMIT 1000" > /tmp/orders_sample.tsv

加上 -N 参数可以去掉表头,如果不加 -N,第一行就是列名。默认输出以制表符分隔,适合快速查看。但如果要保证数据和特殊字符的完整性,还是建议用 INTO OUTFILE,因为客户端重定向在遇到某些特殊字符时不会做转义处理,可能导致数据与原值不一致。

另外,如果你只是想要一份带表头的 CSV 文件,INTO OUTFILE 不会自动生成表头,需要先手工 UNION 一个行再导出,或者导出后用 sed 在文件首行插入表头。这些小技巧在处理交付给业务方的文件时很实用。

4. mysqldump 备份导出的高效用法

4.1 常用关键参数组合

mysqldump 最常见的使用方式是整库导出:

mysqldump -h 127.0.0.1 -uroot -p --single-transaction --quick --default-character-set=utf8mb4 mydb > /backup/mydb_$(date +%F).sql

这个组合有几个参数是必须理解的。--single-transaction 让 InnoDB 表在一个事务内做一致性快照备份,备份过程中不锁表,业务还能继续写入,这是线上备份的首选。--quick 告诉 mysqldump 不要先把所有数据缓冲在内存里再输出,而是边读边写,避免大表备份时内存被打爆。

如果只需要导出某张表的数据而不包含建表语句,可以用:

mysqldump -h 127.0.0.1 -uroot -p --single-transaction --no-create-info mydb orders > /backup/orders_data.sql

反向只导结构不要数据,用 --no-data。这两个参数组合起来,可以灵活拆分结构和数据,在迁移时经常一起用。

4.2 大数据量下的 mysqldump 优化

当数据量上了十多个 GB,mysqldump 的默认行为就会让人捉急。最常见的优化是加 --extended-insert,默认其实已经开启,它的作用是把多行数据合并成一条 INSERT 语句中的多个值,减少执行时的语句开销。如果为了兼容性需要拆成单条 INSERT,可以加 --skip-extended-insert,但导入速度会明显变慢。

管道压缩是另一个立竿见影的手段:

mysqldump -h 127.0.0.1 -uroot -p --single-transaction --quick mydb | gzip > /backup/mydb_$(date +%F).sql.gz

备份文件能小三四倍,传输和磁盘占用都友好很多。导入前先解压:

gunzip -c /backup/mydb_$(date +%F).sql.gz | mysql -h 127.0.0.1 -uroot -p mydb

这里我特别提醒一句:超大库用 mysqldump 还原非常慢,因为它在导入时本质是执行海量 INSERT。如果要恢复几十 GB 以上的数据,在 MySQL 里通常需要考虑物理备份工具或者导入文件用 LOAD DATA INFILE。mysqldump 更适合做逻辑备份、跨版本迁移、异构环境同步。

4.3 导入备份文件时的注意事项

mysqldump 产生的 SQL 文件导入方式有很多种,最简单的就是重定向:

mysql -h 127.0.0.1 -uroot -p mydb < /backup/mydb.sql

也可以进 MySQL 客户端之后用 source:

SOURCE /backup/mydb.sql;

导入时最容易遇到的问题是 max_allowed_packet 不够。mysqldump 默认生成的单条 INSERT 可能非常大,如果目标库的 max_allowed_packet 设置小于单个 INSERT 语句的字节数,会直接报 Packet too large 错误。解决办法是导入前调大:

SET GLOBAL max_allowed_packet = 1073741824;

另外,导入时关闭自动提交和外键检查,也能显著提速:

SET autocommit = 0; SET FOREIGN_KEY_CHECKS = 0; SOURCE /backup/mydb.sql; COMMIT;

如果你的 SQL 文件里有多条 INSERT 数据,打开 autocommit=0 之后要注意,导入结束后务必手动 COMMIT,否则数据可能只停留在当前会话中。

5. 常见问题与排查技巧实录

5.1 高频报错速查表

我把平时遇到的高频问题整理成了一张表,照着排查基本能定位:

报错信息或现象常见原因解决思路
The MySQL server is running with the --secure-file-priv optionsecure_file_priv 默认为 NULL,限制了导入导出路径在配置文件中指定目录,放到该目录下执行
File not found服务端路径不存在或权限不足确认路径相对于 MySQL 服务端,检查文件属主权限
Cannot create fileSELECT INTO OUTFILE 目标文件已存在先删除旧文件,文件名换不重复的
Packet too largemax_allowed_packet 过小调大该参数后重新导入
Incorrect string value字符集不匹配统一文件编码并把 CHARACTER SET 指定为 utf8mb4
Column count doesn't match value count文件列数与表字段数不一致核对 CSV 表头和建表字段,或者用临时变量接收占位
导入后中文字段乱码文件编码与列字符集不一致用 hex 查看文件编码,明确 CHARACTER SET

5.2 三个我踩过的深坑

第一个坑是路径理解错位。我在一台跳板机上执行 LOAD DATA INFILE,文件明明放在跳板机上,却一直在报找不到文件。后来才意识到,不带 LOCAL 时,MySQL 解析文件路径用的是服务端主机视角,跳板机只是客户端,文件根本不在服务端。要么把文件传到服务端指定目录,要么改用 LOAD DATA LOCAL INFILE。

第二个坑是 CSV 中的数据特有逗号。有一次导第三方提供的 CSV,字段里有逗号,但没有用引号包起来,字段数量对不上,整批导入失败。遇到这类文件,先做一次预处理,把多余逗号统一替换或加引号,再执行导入。不要指望 LOAD 语句能智能识别 CSV 的字段边界,它只处理简单的分隔符和引号包裹。

第三个坑是 mysqldump 备份大库时把源库压垮。当时没有加 --single-transaction,默认对 MyISAM 表做全表锁,线上的 SELECT 全部排队。换成 --single-transaction 之后,备份对在线业务的影响降到了最低。如果你的库里有 MyISAM 表,这一点尤其重要,因为 --single-transaction 对 MyISAM 不起作用,需要额外评估或者考虑分区备份。

5.3 导入性能的关键调优思路

同样的文件,导入时间可以从十几分钟压到几十秒,核心思路无非是三条。第一,减少约束检查,暂时关闭外键和唯一性检查,甚至删除非必要索引,导入完后再重建。第二,减少事务开销,导入过程放在一个事务里,避免每行都提交。第三,调整 InnoDB 相关参数,比如合理增大 innodb_buffer_pool_size,让更多数据页在内存中操作,而不是频繁落磁盘。

如果你的数据文件非常大,还可以考虑先按主键或逻辑维度拆分成多个小文件,然后并行执行多个 LOAD DATA INFILE。并行导入的效果受 IO 和 CPU 资源限制,并不是无脑提高并发就一定更快,需要实测。我一般在四核八 GB 以下的小机器上会控制并发数不超过 4 个,再往上提升有限,反而容易出现锁等待和资源争抢。

回到最初那个三十万行导入的问题,用 LOAD DATA INFILE 配合关闭约束检查,几秒钟完成;同样的数据如果用 mysqldump 导出的 SQL 文件去还原,可能就要好几分钟。这两种方式没有谁绝对更好,就看你到底是要“搬文件”还是“搬库”,目标不同,工具就不同。最后再分享一个小习惯:每次导入导出后,我都会用 select count(*) 对比一次源数据和目标库的行数,再抽查几条关键字段,不盲目相信工具的返回状态。数据出入这件事,永远是核对完了才安心。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询