应根据是否有唯一约束选择 insert() 或 upsert():无唯一键用 insert() 分块插入;有业务主键且需幂等则加唯一索引后用 upsert();分块大小依字段复杂度和硬件设为 200–3000,避免事务过长引发锁争用与内存问题。

用 insert() 还是 upsert()?看有没有唯一约束
批量插入的核心分歧不在“快不快”,而在“要不要去重/更新”。insert() 纯追加,简单粗暴;upsert() 能按指定字段(如 email 或 sku)判断是否存在,存在就更新,不存在才插入——但要求数据库表有唯一索引(否则报错或行为异常)。
- 没唯一键、纯日志类数据 → 用
insert(),配合分块(chunk)避免内存爆掉 - 有业务主键(比如商品编码、用户手机号)且需幂等 → 必须加唯一索引,再用
upsert(),否则并发写入会脏数据 - MySQL 8.0.19+ / PostgreSQL 支持
upsert(),SQLite 不支持,Laravel 9+ 才内置该方法
分块大小设多少?别迷信 1000
所谓“分块插入”,本质是把大数组切成小批次交给数据库。设太大(如 5000 行),PHP 内存可能撑不住,MySQL 的 max_allowed_packet 也可能截断;设太小(如 100),IO 次数太多,反而更慢。
- 默认从 1000 开始试,观察
memory_get_peak_usage()和执行时间 - 字段多、含长文本(如 JSON 字段)→ 降到 200~500
- 纯整数/短字符串 + SSD 数据库服务器 → 可试 2000~3000
- 别在事务里塞超过 1 万行:InnoDB 日志(redo log)可能写满,触发强制刷盘卡顿
DB::transaction() 包裹批量操作的陷阱
很多人以为“包个事务就能提速”,其实恰恰相反:事务越长,锁持有时间越久,尤其在 upsert() 场景下,InnoDB 会对匹配的唯一索引范围加间隙锁(gap lock),容易导致其他写请求阻塞。
前后台订单管理页添加商品缩图显示 后台系统设置可直接对商品缩图大小进行设置 去掉商品图片水印功能 上传一张图片,可同时生成列表页缩图及商品详细页缩图,以不同的大小满足页面不同的需要 商品收藏添加批量删除功能 修改商品详细页会员等级显示BUG 优化缩图生成功能(注:因此次优化已更换上传内核,所以有可能会影响已上传商品图片数据) 加入简繁转换 前台订单管理添加单订单在线支付功能 修正VS081样式前台
- 仅当需要原子性(比如插入 A 表同时必须插入 B 表)才用事务包裹整个批次
- 单表批量插入,建议去掉外层事务,让每批独立提交(Laravel 默认就是 auto-commit)
- 真要用事务,务必控制每批 ≤ 500 行,并在
try/catch后显式DB::rollBack(),否则连接可能被占住 - 别在事务里混用
insert()和update()—— 锁类型不同,死锁风险陡增
模型 createMany() 看似方便,实际最慢
createMany() 是 Eloquent 层封装,它会为每一行数据实例化一个 Model 对象,触发全部 boot、booting、creating 回调,还做属性转换和验证(哪怕你关了 $guarded)。对 1 万条数据,光对象创建开销就比原生 insert() 多 3~5 倍。
- 只要不依赖模型事件或自动转换(比如
$casts中的json),一律跳过模型,直连DB::table('xxx')->insert(...) - 需要时间戳?手动加
'created_at' => now(), 'updated_at' => now(),别指望createMany()自动填 - 真要走模型逻辑(比如加密字段),先用
collect($data)->map(...)预处理好数组,再传给insert()
批量入库不是拼函数名有多高级,而是看清数据有没有冲突、数据库能不能扛住、PHP 内存会不会崩——这三个点没理清,换什么方法都白搭。









