I import 5,000 product rows from a CSV nightly. updateOrCreate in a loop is slow. Someone told me to use upsert, but what are the requirements and the differences in Laravel 12?
DEV3 Developer Forum
updateOrCreate vs upsert in Laravel: which one should I use?
Ask a question
Every post is reviewed first. Usually reviewed within 24 hours.
dev3 ·
0
1 reply
dev3 · Accepted answer
They solve similar problems differently.
updateOrCreate
Product::updateOrCreate(['sku' => $row['sku']], ['name' => $row['name'], 'price' => $row['price']]);
One select then an insert or update per call. It fires model events, applies casts and mutators, and handles timestamps. It is not atomic, so two concurrent runs can both insert unless a unique index catches it. Fine for tens of rows.
upsert
Product::upsert(
$rows, // array of arrays
['sku'], // unique columns
['name', 'price'] // columns to update on conflict
);
A single INSERT ... ON DUPLICATE KEY UPDATE (MySQL) or ON CONFLICT (Postgres/SQLite) statement per batch. Much faster, and atomic per statement.
Requirements and caveats
- The "unique by" columns need a primary or unique index in the database on every driver except SQL Server. MariaDB and MySQL ignore the argument and use the table's primary and unique indexes, while SQLite and Postgres need the columns to match a unique index exactly.
- No model events, no casts, no accessors: you must pass database-ready values (JSON encoded, enum values, dates as strings). Timestamps:
Model::upsert()fillscreated_at/updated_atfor you on models that use timestamps (the query builder'sDB::table()->upsert()does not). - Chunk large imports to avoid parameter limits:
collect($rows)->chunk(1000)->each(fn ($c) => Product::upsert($c->all(), ['sku'], ['name','price'])). - Rows are not matched by
whereconditions, only by index.
Other helpers
firstOrCreate: create only if absent, never updates.insertOrIgnore: skip duplicates silently.Product::query()->where(...)->update([...])for bulk updates by condition.
For import jobs also wrap the loop in a queue job and log invalid rows rather than failing the whole batch.
0