批量加载700万个CSV文件至数据库表有哪些潜在问题?
兄弟,700万个CSV文件用你这个脚本导入?这规模可比你之前测的31个量级差太远了,绝对会踩一堆坑,我给你逐个掰扯清楚:
1. 进程与连接资源直接炸锅
你的脚本每循环一次就启动一个全新的mysql进程,还要和远程数据库建立一次TCP连接——700万次啊!
- 客户端这边:频繁创建销毁进程会把CPU、内存吃光,系统进程表很快就会被占满,轻则卡顿,直接触发OOM(内存不足)导致系统崩溃都有可能。
- 数据库端:700万次连接请求会直接打穿数据库的最大连接数限制,轻则拒绝新连接,重则把数据库服务搞挂。
2. 效率低到令人发指
单个LOAD DATA请求本身就有连接、认证、初始化的开销,700万次重复这些操作,时间成本会指数级上升。之前31个文件感觉不到,换成700万的话,可能跑几天甚至几周都完不成,完全是在浪费资源。而且你完全没利用LOAD DATA的批量优势,明明可以合并文件批量导入,非要单个来。
3. 零错误处理,出问题全白搭
你的脚本里没有任何错误检查机制!如果某个CSV文件损坏(比如某行少了字段)、权限不足读不了、或者格式不符合要求,mysql命令会直接失败,但脚本会继续往下跑。等你最后发现数据不全的时候,根本不知道哪些文件没导入,要重新跑的话又得从头再来,完全没有断点续传的可能,纯纯的做无用功。
4. 数据库端性能瓶颈直接拉满
就算客户端扛得住,数据库服务器也顶不住700万次的LOAD DATA请求:
- 每次导入都会触发索引更新、事务日志写入(如果用的是InnoDB引擎),频繁的小事务会让redo log刷写极其频繁,直接把磁盘IO打满。
- 大量小批量导入会导致索引碎片疯狂增加,后续查询性能会暴跌,到时候还要花时间去优化索引,得不偿失。
5. 文件名与路径的隐形坑
你用for f in $(find /datafiles -type f)遍历文件,要是某个文件名里有空格、&、*这类特殊字符,这个循环会把文件名拆成多个元素,导致mysql命令里的${f}完全不对,甚至可能执行一些意外的命令,有安全风险。另外你开头的cd /datafiles其实没用,因为find输出的是绝对路径,但这个小问题在大规模场景下也可能引发意外。
6. LOCAL参数与权限的暗坑
LOAD DATA LOCAL INFILE这个命令需要mysql客户端开启local_infile参数(很多版本默认是禁用的),如果客户端或服务器端没开,会直接报错。而且如果运行脚本的用户没有读取某个CSV文件的权限,也会导入失败,但你的脚本不会给任何提示,直接跳过,你根本不知道。
7. 监控与日志完全空白
脚本里只有echo $f输出文件名,700万条输出会把终端或者日志文件撑爆,而且没有记录每个文件的导入状态(成功/失败)。你根本没办法实时监控进度,也不知道什么时候出了问题、出了什么问题,完全是瞎跑。
给你几个快速优化的小建议
- 先把多个小CSV合并成大文件(比如按目录或者按大小合并,比如每个大文件包含1000个小文件),减少
mysql进程的启动次数。 - 用单个mysql连接批量执行
LOAD DATA,或者用mysqlimport工具,它专门针对批量导入做了优化,效率高很多。 - 给脚本加错误处理:比如
mysql ... || echo "Failed to import: $f" >> import_errors.log,把失败的文件记录下来,方便后续补导。 - 数据库端先临时关闭索引,导入完成后再重建;调整redo log大小,开启批量提交,减少IO压力。
- 把遍历方式换成
find /datafiles -type f | while read -r f; do ... done,这样能正确处理带特殊字符的文件名。
内容的提问来源于stack exchange,提问作者A B

