Laravel query builder upsert beats Eloquent line-by-line processing by ten times
The performance gap in high-volume upserts
When managing e-commerce catalogs or large-scale data synchronizations, developers often face the challenge of merging a million-row CSV into an existing database. The choice of implementation strategy in Laravel determines whether a background job takes five minutes or thirty seconds. While the updateOrCreate() method is the most readable and developer-friendly, it creates a massive performance bottleneck because it executes individual database queries for every single row.
Prerequisites and Toolkit
To implement these high-performance patterns, you should be comfortable with PHP and the Laravel framework. Specifically, you need to understand how the Query Builder interacts with your database driver. For these benchmarks, MySQL was used on high-end hardware, but the logic applies across different environments.

Key Libraries & Tools
- Query Builder: A fluent interface for creating and running database queries without the overhead of the full ORM.
- Eloquent ORM: Laravel's implementation of the active record pattern, useful for model-based logic.
- MySQL: The primary database engine used for benchmarking these bulk operations.
Code Walkthrough: Implementing the Upsert
The most efficient way to handle massive updates is the native upsert() method. Unlike line-by-line processing, this sends data in batches, significantly reducing the round-trip time between the application and the database.
DB::table('products')->upsert([
['sku' => 'PROD-1', 'price' => 100, 'updated_at' => now()],
['sku' => 'PROD-2', 'price' => 150, 'updated_at' => now()],
], ['sku'], ['price', 'updated_at']);
The first argument is the array of values. The second argument, ['sku'], identifies the unique column that determines whether to insert or update. The third argument specifies which columns should be updated if a match is found.
Syntax Notes and Best Practices
A critical distinction exists between Eloquent ORM and the Query Builder when using upsert(). The Query Builder does not automatically manage created_at or updated_at timestamps. You must manually include these in your data array. Conversely, Eloquent ORM handles timestamps but bypasses model events and observers during an upsert. If your application relies on Saved or Created events to trigger secondary logic, the bulk upsert will break that workflow.
The failure of PHP-side smart splitting
One might assume that splitting data into "new" and "existing" sets within PHP before sending queries would save time. However, benchmarks prove this "smart split" approach is actually slower. Loading a million records into memory to perform comparisons in PHP consumes excessive RAM and adds roughly 20 seconds of overhead just for data preparation. Offloading logic to the database engine remains the gold standard for performance.
- Query Builder
- 25%· products
- Eloquent ORM
- 19%· products
- Laravel
- 13%· products
- MySQL
- 13%· products
- DigitalOcean
- 6%· companies
- Other topics
- 25%

Laravel Upsert 1M Rows into MySQL from CSV: 5 Ways with Benchmarks
WatchLaravel Daily // 15:39