- Laravel
- eloquent
- performance
- database
Use chunkById() when the loop changes the rows it reads
chunk() pages with OFFSET, so updating a row that leaves the WHERE clause shifts the next page and skips rows. chunkById() pages by the primary key and does not.
1 min read
The classic bug: process every unprocessed row in chunks, mark each as processed. Half of them never get touched.
Order::where('processed', false)
->chunkById(500, function ($orders) {
foreach ($orders as $order) {
$order->update(['processed' => true]);
}
});chunk() runs LIMIT 500 OFFSET 500 for page two, but the first 500 rows no longer match processed = false, so page two starts 500 rows too far. chunkById() remembers the last id and asks for id > 2037 instead, which never shifts.
For reading only, lazyById() does the same with a LazyCollection and keeps one chunk in memory at a time.