Skip to content
OpenCloseEsc
Tips
  • 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.