İçeriğe geç
Muhammet Şafak
en
Soran: Naz Cevaplandı:

İş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.

  1. chunk() neden atlıyor. chunk() her sayfa için LIMIT/OFFSET ile ayrı bir sorgu atar. İlk 500 satırı işleyip status’ü processed yapınca bu satırlar where status = pending filtresinden düşer; ikinci sayfada OFFSET 500 artık daralmış bir sonuç kümesine uygulanır ve tam ortadaki bir dilim hiç görülmeden atlanır.
  2. 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']);
            }
        });
  3. 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.
  4. Bellek için lazyById(). Aynı seek mantığını kullanır ama closure yerine tek bir LazyCollection döner; foreach ile akıcı yazmak isterseniz, üstelik büyük tablolarda RAM’i şişirmeden, daha temiz olur.
  5. 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';
  6. 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.

Paylaş:

Yorumlar

Yorum yapmak için GitHub hesabınızla giriş yapmanız yeterli. Yorumlar GitHub Discussions üzerinde saklanır.

Diğer Sorular

Tüm sorular

Sitede Ara

Yazı, proje ve sayfalarda arama yapmak için yazmaya başlayın.

Esc ile kapat Pagefind ile güçlendirildi