用 DB::table()->insert() 批量插入可将10万条数据耗时从3–5分钟降至2–5秒,因避免了Eloquent生命周期开销;需注意字段顺序、单批≤1000行、流式读取、禁用日志与事件、合理使用INSERT IGNORE等。

用 DB::table()->insert() 批量插,别用 Model::create()
单条 Model::create() 插入 10 万条数据,通常要 3–5 分钟,还可能 OOM;换成原生批量插入,基本能压到 2–5 秒。根本原因是:前者每条都走 Eloquent 生命周期(事件、访问器、类型转换、验证模拟),后者只是拼 SQL + 一次 PDO 执行。
实操建议:
-
insert()接收二维数组,每项是字段名 => 值的关联数组,字段顺序必须一致(PDO 不认键名,靠位置绑定) - 单次插入别超 1000 行,MySQL 默认
max_allowed_packet通常为 4MB,1000 行含中等长度字符串一般刚好卡在安全边界 - 避免混用
NULL和空字符串,字段定义为NOT NULL时,NULL会直接报错SQLSTATE[HY000]: General error: 1364 Field doesn't have a default value - 时间字段统一用
date('Y-m-d H:i:s'),别传Carbon实例——insert()不会自动调用toDateTimeString()
手动分 chunk 写死批次大小,别信 Collection::chunk() 的默认行为
Collection::chunk(1000) 看似省事,但底层是 array_chunk(),对超大数组会一次性把全部数据载入内存。读 100 万行 CSV 后再 chunk,PHP 进程 RSS 很快飙到 1.2GB+,直接被系统 kill。
正确做法是边读边插:
- 用
fopen()+fgetcsv()流式读 CSV,每次只 hold 一行原始数据 - 攒够 500–1000 行再调
DB::table()->insert($batch),插完清空$batch = [] - 在循环里加
if (count($batch) >= 1000) { ...; $batch = []; },比依赖 Collection 更可控 - 注意:
DB::transaction()在这里没意义——单个insert()本身就是原子操作,套事务反而增加锁等待
关掉查询日志和模型事件,否则速度直接打五折
Laravel 开发环境默认开启 DB::enableQueryLog(),且 Eloquent 模型默认触发 creating/created 事件。哪怕你没监听,框架仍会遍历空事件列表 + 记录每条 SQL 到内存,10 万条就是 10 万次无谓循环。
Laravel Cloud(cloud.laravel.com)。此技能用于处理所有 Laravel Cloud 请求,包括搜索和读取数据。只要任务涉及 Laravel Cloud,即使用此技能。
上线前必须确认:
-
APP_DEBUG=false,否则DB::getQueryLog()自动启用 - 临时禁用模型事件:
User::unsetEventDispatcher()(插完记得恢复) - 如果用了自定义的
boot()里写日志或统计,批量时务必包一层if (app()->environment('local')) { ... } - 检查是否启用了 Telescope 或 Laravel Debugbar——它们会拦截并存储每条查询,批量插入时极易撑爆 Redis 或本地文件
INSERT IGNORE / ON DUPLICATE KEY UPDATE 性能差很多,选错语法会拖慢 3 倍
有唯一索引冲突处理需求时,INSERT IGNORE 比 ON DUPLICATE KEY UPDATE 快得多,因为前者是“遇到冲突直接跳过”,后者要先查再更新,多一次索引查找。
但注意:
-
INSERT IGNORE静默丢弃整行,不报错也不返回影响行数,调试时容易误以为数据丢了 -
ON DUPLICATE KEY UPDATE要显式列出所有需更新字段,漏写updated_at就不会自动刷新时间戳 - 别在批量中混用两种逻辑——比如部分行用
IGNORE,部分用UPDATE,只能拆成两次insert()调用,失去批量意义 - 如果表有触发器,
INSERT IGNORE仍会触发BEFORE INSERT,但不会进AFTER INSERT;而ON DUPLICATE KEY UPDATE会走BEFORE UPDATE和AFTER UPDATE
真正卡点不在语法本身,而在你有没有提前建好覆盖所有 INSERT 条件的联合唯一索引——没索引时,IGNORE 和 UPDATE 都会全表扫描去判重,速度直接归零。










