chunkById is the safe one when the loop mutates rows

chunk() paginates with OFFSET, so if the loop body changes whether a row still matches the query, the offset for the next page has shifted and rows get skipped. Updating a flag you are also filtering on will silently process about half the table.

// skips rows: each update shrinks the result set under the offset
Order::where('exported', false)->chunk(500, function ($orders) {
    $orders->each->markExported();
});

// safe: pages by id, not by position
Order::where('exported', false)->chunkById(500, function ($orders) {
    $orders->each->markExported();
});

chunkById() remembers the last id and asks for rows after it, so the window does not move when rows leave the set. It requires an ordered, unique column and quietly overrides any ORDER BY you had. As a rule: read-only iteration can use chunk(), anything that writes should not.