# Sadece 'pending' satırları taranan bir kuyruk tablosunda partial index kullanmalı mıyım?

> `WHERE status='pending'` partial index'ine `ORDER BY` kolonunu da katın, `FOR UPDATE SKIP LOCKED` ile eşleyin ve `'pending'`'i sorguya literal geçin.

- Soruldu: 2026-08-08
- Yanıtlandı: 2026-08-11
- Soran: Yiğit
- Etiketler: postgresql, veritabani, performans
- Kaynak: https://muhammetsafak.com/tr/sor-bakalim/sadece-pending-satirlari-taranan-bir-kuyruk-tablosunda-partial-index-kullanmali-miyim/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
**Soru:** Postgres'te bir `jobs` kuyruk tablom var. İçinde milyonlarca `completed` satır birikiyor ama ben yalnızca birkaç bin `pending` satırı sorguluyorum: worker'lar `WHERE status = 'pending' ORDER BY created_at` ile sıradaki işi çekiyor.

`status` üzerine normal bir index mi, yoksa `WHERE status = 'pending'` koşullu bir partial index mi kullanmalıyım? Partial index'i kullanırken planner'ın onu gerçekten seçmesi için nelere dikkat etmeliyim?


Kısa cevap: Evet, partial index tam da bu senaryonun ders kitabı örneğidir.

## Kısa cevap

`WHERE status = 'pending'` koşullu bir index yalnızca birkaç bin satırı tutar; küçük olur, belleğe sığar, taraması hızlı ve güncellemesi ucuzdur — milyonlarca ölü satıra hiç dokunmadan geçer. Index'in hangi kolonları, hangi sırayla içermesi gerektiğini [composite index kolon sırası kaydında](/tr/sor-bakalim/postgresql-composite-index-kolon-sirasi-nasil-secilir/) ayrıca ele almıştım; buradaki mesele o index'i ayrıca daraltmak.

## Neden

1. **Partial index yalnızca eşleşen satırları indeksler.** `CREATE INDEX ... WHERE status='pending'` ile index milyonlarca değil birkaç bin girdi tutar. Bu, onu neredeyse tamamen cache'te tutar ve her insert/update'te güncellenmesi gereken ağacı küçültür.

2. **Status üzerine düz index'ten kesinlikle daha iyidir.** Düşük kardinaliteli `status` kolonuna düz bir index'in büyük kısmı işe yaramaz `done` değerleridir; planner çoğu zaman onu es geçip seq scan yapar. Bu erişim deseni için partial index net üstündür.

3. **Aynı mantık her çarpık predicate için geçerlidir.** Ders yalnızca kuyruklara özgü değil: `WHERE deleted_at IS NULL` (soft-delete), `WHERE processed = false` gibi tablonun küçük bir azınlığını hedefleyen her sorguda partial index aynı kazancı verir. "Az sayıda satırı, çok sayıda ölü satırın arasından" çektiğiniz her yerde aklınıza gelsin.

## Ne yapmalı

1. **ORDER BY kolonunu index'e koyun.** Worker'lar tipik olarak `WHERE status='pending' ORDER BY created_at FOR UPDATE SKIP LOCKED` yapar. Sıralama anahtarını index'e dahil edin: `(created_at) WHERE status='pending'`. Böylece hem index sırasında tarama alırsınız hem de `SKIP LOCKED` ile kilitli satırları atlayıp sıradaki işi anında çekersiniz. SQL şöyle:

   ```sql
   CREATE INDEX idx_jobs_pending
       ON jobs (created_at)
       WHERE status = 'pending';

   -- worker'ın çekiş sorgusu:
   SELECT id, payload
   FROM jobs
   WHERE status = 'pending'
   ORDER BY created_at
   FOR UPDATE SKIP LOCKED
   LIMIT 1;
   ```

2. **Predicate'i sorguda literal geçin.** Sorgunuzun `WHERE`'i, index predicate'inin (`status='pending'`) mantıksal olarak kapsadığı bir koşul olmalı. `status = $1` gibi parametreyle sorarsanız planner index'i seçemeyebilir; koşulu literal `'pending'` olarak yazın ki eşleşme kanıtlanabilsin.

3. **Churn ve bloat'a dikkat.** Bir satır `pending`'den `done`'a döndüğünde partial index'ten düşer — bu iyi. Ama yoğun update trafiği bloat üretebilir; autovacuum'un sağlıklı çalışması önemli. İyi haber: index küçük olduğu için vacuum'u da ucuzdur.

**Sonuç:** Ben olsam çekiş predicate'i üzerine, `ORDER BY` kolonunu da içerecek şekilde bir partial index kurar ve onu `FOR UPDATE SKIP LOCKED` ile eşlerdim. Tek dikkat noktası: sorgularınızın `'pending'`'i literal geçmesi ki planner index'i gerçekten seçsin. `EXPLAIN (ANALYZE, BUFFERS)` ile index scan aldığınızı bir kez doğrulayın; sonra milyonlarca `completed` satırın birikmesine rahatça aldırmazsınız.

## İlgili Yazılar

- [Heap erişimini önlemek ve index-only scan almak için covering index'e (INCLUDE) ne zaman geçmeliyim?](https://muhammetsafak.com/tr/sor-bakalim/index-only-scan-covering-indexe-ne-zaman-gecmeliyim/) — Sor Bakalım
- [PgBouncer'da Session/Transaction/Statement modlarından hangisini seçmeliyim?](https://muhammetsafak.com/tr/sor-bakalim/pgbouncer-session-transaction-statement-modu-ve-prepared-statement/) — Sor Bakalım
- [PostgreSQL'de composite index kolon sırasını nasıl seçerim?](https://muhammetsafak.com/tr/sor-bakalim/postgresql-composite-index-kolon-sirasi-nasil-secilir/) — Sor Bakalım
