用 insert() 还是 upsert() 取决于是否需处理唯一冲突:无重复用 insert() 最快;可能重复则用 upsert() 并指定 $uniqueBy,否则退化为普通插入。

用 insert() 还是 upsert()?看有没有唯一冲突
批量插入的核心分歧不在“快不快”,而在“数据会不会重复”。insert() 最简单,但遇到重复主键或唯一索引会直接报错 SQLSTATE[23000]: Integrity constraint violation;upsert()(Laravel 9+)能自动忽略或更新冲突行,代价是底层生成 INSERT ... ON CONFLICT(PostgreSQL)或 INSERT ... ON DUPLICATE KEY UPDATE(MySQL),语法和语义都更重。
- 纯新增、确定无重复:用
insert(),最快最轻量 - 可能混杂旧数据、需去重或覆盖:用
upsert(),但必须显式传$uniqueBy参数,比如['email']或['id'],漏写就退化成普通插入 - MySQL 5.7 以下不支持
upsert(),会抛出NotSupportedException
分批插比一次插 10 万条更稳
Laravel 的 insert() 和 upsert() 默认把所有数据拼成一条 SQL,数据量大时容易触发 MySQL 的 max_allowed_packet 限制(默认 4MB),报错 Packets larger than max_allowed_packet are not allowed。不是 PHP 内存爆了,是 MySQL 拒收大包。
- 按 1000 行/批切分最稳妥,
collect($data)->chunk(1000)是常用写法 - 别用
DB::transaction()包整个大批次——锁表时间太长,其他请求会被堵住 - 如果要保证原子性,就在每个 chunk 内加事务,而不是跨 chunk
- 注意:Eloquent 模型的
createMany()不做分批,它只是封装了insert(),大数据量下一样会撞包大小限制
跳过模型事件和强制类型转换能省 30%+ 时间
用 Model::insert() 是走查询构造器,不实例化模型,自然不触发 creating、saving 等事件,也不执行 $casts 或 mutators。但很多人误用 Model::createMany(),它会逐条 new Model → fill → save,性能断崖下跌。
- 确认字段值已格式化好(比如时间转为 Y-m-d H:i:s,布尔转为 0/1),再走
insert() - 如果必须用模型逻辑(如自动填充
created_at),改用Model::upsert()并配好$uniqueBy,它至少避免了实例化开销 -
insert()返回布尔值,不是模型实例;别指望它返回 ID 或触发created事件
MySQL 批量插入前关掉 autocommit 更快,但得自己兜底
在命令行或 DB 连接层临时关掉自动提交(SET autocommit = 0),再执行多条 INSERT,最后 COMMIT,能显著减少日志刷盘次数。但 Laravel 默认每条查询都是独立事务,没法直接套用。
- 可用
DB::unprepared()手动发原生 SQL,但失去 Query Builder 的安全过滤,字符串拼接易 SQL 注入 - 更安全的做法:用
DB::transaction()包裹一批insert(),Laravel 会自动在事务开头关 autocommit,结尾 commit - 注意:事务中任何一条失败,整批回滚,得做好错误捕获和重试逻辑,不能只靠 try/catch 打印一句 “插入失败”
真正卡住的往往不是语法,而是没意识到 insert 是“裸 SQL 拼接”,而 createMany 是“模型循环实例化”——这两条路从第一行代码起就走向了不同的性能轨道。


















