İşlediğim satırları aynı anda güncellerken chunk yerine chunkById mı kullanmalıyım?
Soru
Laravel'de büyük bir tabloyu batch bir job ile işliyorum. Kayıtları `where('status', 'pending')` ile filtreleyip `chunk()` ile geziyorum ve her satırda `status` kolonunu `pending`'den `processed`'e güncelliyorum. Sorun şu: bazı satırlar hiç işlenmeden atlanıyor. Sanırım güncellediğim satırlar filtreden düşünce OFFSET kayıyor ve altındaki kayıtlar es geçiliyor. `chunk()` yerine `chunkById()` mı kullanmalıyım? Bu skip problemini gerçekten çözer mi?
Cevap
Kısa cevap: Evet, chunkById() kullanın. Gördüğünüz atlama, chunk()’ın OFFSET tabanlı sayfalamasının bilinen bir tuzağıdır ve sizinki tam ders kitabı örneği. Tek bir koşul var: cursor kolonunu (genelde id) asla güncellememeniz.
chunk()neden atlıyor.chunk()her sayfa içinLIMIT/OFFSETile ayrı bir sorgu atar. İlk 500 satırı işleyipstatus’üprocessedyapınca bu satırlarwhere status = pendingfiltresinden düşer; ikinci sayfadaOFFSET 500artık daralmış bir sonuç kümesine uygulanır ve tam ortadaki bir dilim hiç görülmeden atlanır.chunkById()nasıl çözer. OFFSET yerine keyset (seek) sayfalama yapar:WHERE id > ? ORDER BY id LIMIT n. Bir sonraki sayfa daima son işlenen id’den devam eder; işlenenler zaten cursor’ın gerisinde kaldığı için “kayma” diye bir şey olmaz.Order::where('status', 'pending') ->chunkById(500, function ($orders) { foreach ($orders as $order) { $order->update(['status' => 'processed']); } });- Kritik kural: cursor kolonunu güncellemeyin.
status’ü güncellemek sorun değil, çünkü o cursor değil. Ama sıraladığınız kolonu (id) döngü içinde değiştirirseniz seek mantığı bozulur ve yine tutarsızlık yaşarsınız. - Bellek için
lazyById(). Aynı seek mantığını kullanır ama closure yerine tek birLazyCollectiondöner;foreachile akıcı yazmak isterseniz, üstelik büyük tablolarda RAM’i şişirmeden, daha temiz olur. - En hızlısı çoğu zaman iterasyon değildir. Güncelleme deterministikse (satır başına hesaplama yoksa) satır satır dönmeye hiç gerek yok; tek bir set-based sorgu hepsinden hızlıdır ve atomiktir:
UPDATE orders SET status = 'processed' WHERE status = 'pending'; - Lock ve deadlock’a dikkat. Büyük batch update’lerde chunk boyutunu makul tutun (500–1000 iyi bir başlangıç) ve gerekiyorsa her chunk’ı kendi transaction’ında işleyin; tek dev transaction hem lock süresini hem deadlock riskini büyütür.
Sonuç: Ben olsam satır başına gerçek bir iş varsa chunkById() (ya da akıcılık için lazyById()) kullanır ve cursor kolonuna asla dokunmazdım. Ama yaptığınız iş sadece bir kolonu güncellemekse, iterasyonu tamamen bırakıp tek bir set-based UPDATE’e geçerdim — hem daha hızlı hem de skip riski sıfır.
Yorumlar
Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.