如何使用Laravel批量插入或更新大量数据? (upsert与insert方法)

11次阅读

应根据语义需求选择:单纯追加用 insert,去重更新用 upsert;但 upsert 依赖数据库唯一索引与驱动支持,mysql 8.0.19+、postgresql 可用,sqlite 部分支持,SQL Server 不支持。

如何使用Laravel批量插入或更新大量数据? (upsert与insert方法)

upsert 还是 insert,取决于你是否需要「存在则更新、不存在则插入」的语义。如果只是单纯追加数据,insert 更快更轻量;如果要避免重复、保持唯一性且批量处理,upsertlaravel 9+ 提供的正确选择——但它的行为和底层 SQL 有强绑定,不注意会 silently 失败。

upsert 的实际行为依赖数据库驱动支持

upsert 在 MySQL 和 PostgreSQL 中表现不同,SQLite 仅部分支持,SQL Server 完全不支持(Laravel 会退化为逐条 insert + update)。调用前必须确认你的数据库版本和驱动配置:

  • MySQL 8.0.19+ 支持 INSERT ... ON DUPLICATE KEY UPDATE,Laravel 会自动映射
  • PostgreSQL 需要 ON CONFLICT 子句,且 $uniqueBy 参数必须对应表中已存在的 UNIQUEPRIMARY KEY 约束字段
  • 若指定的 $uniqueBy 字段没有对应唯一索引,MySQL 会报错 1062 Duplicate entry,PostgreSQL 直接抛出 SQLSTATE[42703]: undefined column
DB::table('users')->upsert(     [         ['email' => 'a@example.com', 'name' => 'Alice', 'score' => 100],         ['email' => 'b@example.com', 'name' => 'Bob',   'score' => 85],     ],     ['email'], // $uniqueBy:必须是数据库中已有 UNIQUE 约束的字段     ['name', 'score'] // $updateColumns:只更新这些字段,其他字段(如 created_at)不会被 touch );

insert 不做冲突检测,但大数据量时要注意内存与事务

insert 只负责批量写入,不查重、不更新。适合日志、埋点、导入原始数据等场景。问题在于:一次塞几万条容易 OOM 或触发 MySQL max_allowed_packet 限制。

  • 建议按 1000 行分批,用循环 + DB::transaction() 包裹
  • 不要把整个 csv 解析后一次性 array_chunk 再传给 insert,而应在文件流读取过程中边解析边插入
  • 使用 DB::statement() 手动拼接多值 INSERT(如 INSERT INTO t VALUES (),(),()...)比 Eloquent 的 insert() 快 3–5 倍,但需自己处理 SQL 注入(务必用 DB::raw() 和参数绑定)
foreach (array_chunk($data, 1000) as $chunk) {     DB::table('events')->insert($chunk); }

upsert 的 $updateColumns 参数不能包含主键或唯一键

这是最容易踩的坑:$updateColumns 列表里如果误写了 idemail(而 email 又在 $uniqueBy 里),PostgreSQL 会报 ON CONFLICT DO UPDATE command cannot affect row a second time,MySQL 则可能静默忽略更新或报错。Laravel 不校验这个逻辑,全靠开发者自查。

  • 正确做法:$updateColumns 只放业务字段,比如 ['status', 'updated_at']
  • 时间戳字段要显式写进 $updateColumns,否则 updated_at 不会自动更新
  • 如果想让 updated_at 自动设为当前时间,得配合 DB::raw('NOW()') 或在模型中启用 timestamps 并手动赋值

真正麻烦的不是语法,而是 upsert 背后那层数据库约束——没建好唯一索引,它就不是 upsert,只是个会崩的 insert。别跳过 php artisan migrate 里的 Schema::unique() 步骤。

text=ZqhQzanResources