用 insert() 还是 upsert() 取决于是否允许重复:insert() 遇唯一冲突直接报错,upsert() 可忽略或更新冲突行,但需数据库和 laravel 版本支持;有唯一字段且需去重时选 upsert(),纯新增场景用 insert()。

用 insert() 还是 upsert()?看有没有唯一冲突
批量插入的核心分歧不在“快不快”,而在“数据会不会重复”。insert() 最简单,但遇到重复主键或唯一索引会直接报错 SQLSTATE[23000]: Integrity constraint violation;upsert() 能自动忽略或更新冲突行,但只支持 MySQL 8.0.19+、PostgreSQL 和 SQLite 3.24+,Laravel 9+ 才原生支持。
- 纯新增场景(比如日志、埋点、导入无主键原始数据):用
insert(),传二维数组,一次发一条 SQL - 有唯一字段(如
email或sku),且希望跳过重复或更新已存在记录:用upsert(),必须显式指定$uniqueBy参数,比如['email'] - 旧版本 Laravel(upsert()?别硬套包,改用
DB::statement()拼INSERT ... ON DUPLICATE KEY UPDATE更稳
批量插入时内存爆了?别一次性塞 10 万条
Laravel 的 insert() 底层仍是 PDO 预处理,但 PHP 数组本身吃内存。插 5 万条含 10 个字段的记录,未压缩数组可能占 200MB+,超 memory_limit 就直接 Fatal error: Allowed memory size exhausted。
- 安全上限建议:单次
insert()不超过 1000 行,用collect($data)->chunk(1000)切分 - 别在事务里堆所有 chunk:外层开事务 + 每个 chunk 内单独
DB::transaction()会嵌套失败;统一在外层包住全部 chunk - 如果数据源是文件或 API 流式输入,边读边 chunk,别先
file_get_contents()全读进内存
用 DB::table()->insert() 还是 Model::insert()?
两者最终都调 Illuminate\Database\Query\Builder::insert(),性能几乎无差别,但行为差异很实际:
-
DB::table('users')->insert($rows):绕过 Eloquent,不触发creating/created事件,也不走casts、mutators,时间字段不会自动转Y-m-d H:i:s -
User::insert($rows):走完整模型生命周期,但注意——insert()是静态方法,不会调save(),所以boot()里的creating仍会执行,而saved不会 - 需要软删除字段(
deleted_at)自动设为null?必须用Model::insert(),否则得手动补'deleted_at' => null
为什么加了索引反而更慢?批量插入前先删索引
插入时每行都要更新 B+ 树索引,10 万条数据 + 3 个二级索引 ≈ 多写 30 万次磁盘页。实测 MySQL 下,关掉非必要索引后插入速度可提升 3–5 倍。
- 只禁用非主键、非外键、非业务强依赖的索引,比如
created_at查询频次低的辅助索引 - 操作顺序必须是:
DB::statement('ALTER TABLE users DROP INDEX idx_created_at')→ 批量插入 →DB::statement('CREATE INDEX idx_created_at ON users(created_at)') - 别在生产环境凌晨跑这个:DDL 语句会锁表,MySQL 5.7 下
DROP INDEX是重建表,时间不可控
真正卡顿的地方往往不是语法怎么写,而是没想清楚“这批数据到底要不要立刻可查”——如果只是中间表或临时分析用,索引完全可以等插完再建。











