# PostgreSQL'de composite index kolon sırasını nasıl seçerim?

> Tek bir (user_id, status, created_at DESC) composite index kurun: eşitlik kolonları başa, sıralama kolonu sona; kapsanan tekli indeksleri silin.

- Soruldu: 2026-05-17
- Yanıtlandı: 2026-05-20
- Soran: Doğa
- Etiketler: performans, postgresql, veritabani
- Kaynak: https://www.muhammetsafak.com.tr/sor-bakalim/postgresql-composite-index-kolon-sirasi-nasil-secilir/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
**Soru:** Milyonlarca satırlı bir `orders` tablom var ve en sık sorgum `WHERE user_id = X AND status = 'completed' ORDER BY created_at DESC`. Şu an çok yavaş çalışıyor; tabloda `user_id`, `status` ve `created_at` için ayrı ayrı tekli indeksler var, DB bunları Bitmap Index Scan ile birleştirmeye çalışınca maliyet patlıyor.

En verimli composite index sırası ne olmalı? Kolonların seçicilik (cardinality) oranı bu sırayı nasıl etkiliyor?


Kısa cevap: Bu sorgu için tek bir composite index kur — `(user_id, status, created_at DESC)`. Eşitlik kolonları başa, ORDER BY kolonu en sona; gerisi kendiliğinden çözülür.

Yaşadığın yavaşlık index eksikliği değil, **yanlış index biçimi**: üç ayrı tekli indeksi DB Bitmap Index Scan ile birleştirip sonra ayrı bir Sort adımı çalıştırmak zorunda kalıyor, ikisi de pahalı.

1. **Önce eşitlik kolonları, sonra sıralama kolonu.** Kural net: `WHERE`'de eşitlikle (`=`) süzdüğün kolonlar index'in başında olur — burada `user_id` ve `status`. ORDER BY'a giren kolon ise **en sona** gelir: `(user_id, status, created_at DESC)`. Böylece B-tree, eşitlik filtresini uyguladıktan sonra satırları zaten `created_at DESC` sırasında verir; ayrı Sort adımına gerek kalmaz.
2. **`DESC`'i index'e yaz.** Sıralaman tek yönlüyse (`created_at DESC`) bunu index tanımına koy. Tek kolonda PostgreSQL index'i geriye de tarayabilir, ama composite'te yönü sabitlemek planlayıcının işini garantiler ve sıralamayı bedavaya getirir.
3. **Cardinality eşit eşitliklerde ikincil.** Cardinality, hangi eşitlik kolonunun önde olacağını etkiler; ama burada ikisi de eşitlik predicate'i, yani asıl kazanç sıralamayı index'e taşımakta. Yine de yüksek seçicilikli kolonu (genelde `user_id`) öne koymak ilk taramayı daraltır.
4. **`EXPLAIN (ANALYZE, BUFFERS)` ile doğrula.** İstediğin plan tek bir **Index Scan** — `Bitmap Index Scan` + `Sort` değil. Planda hâlâ Sort görüyorsan kolon sırası ya da `DESC` yönü hatalıdır.
5. **`completed` baskınsa partial index düşün.** Sorgularının çoğu `status = 'completed'` ise `WHERE status = 'completed'` koşullu **partial index** kullan: index boyutunu küçültür, RAM'de daha çok tutabilir, yazma maliyetini düşürür.

**Sonuç:** Ben olsam `(user_id, status, created_at DESC)` composite index'ini kurar, `EXPLAIN (ANALYZE, BUFFERS)` ile Index Scan'e düştüğünü doğrular ve bu index'in zaten kapsadığı tekli `user_id`/`status` indekslerini **silerdim** — fazlalık index sadece yazmayı yavaşlatır. Trafiğin tek bir status'e yığılıyorsa partial index'le bir tur daha sık. Indeksleme ile native SQL'in dengesi üzerine daha derin bir tartışma için sade.dev'deki yazıya bak.

## İlgili Yazılar

- [PostgreSQL Her Şeye Yeter mi?](https://sade.dev/tr/journal/postgresql-her-seye-yeter-mi) — sade.dev
- [Sadece 'pending' satırları taranan bir kuyruk tablosunda partial index kullanmalı mıyım?](https://www.muhammetsafak.com.tr/sor-bakalim/sadece-pending-satirlari-taranan-bir-kuyruk-tablosunda-partial-index-kullanmali-miyim/) — Sor Bakalım
- [PgBouncer'da Session/Transaction/Statement modlarından hangisini seçmeliyim?](https://www.muhammetsafak.com.tr/sor-bakalim/pgbouncer-session-transaction-statement-modu-ve-prepared-statement/) — Sor Bakalım
- [Sürekli güncellenen bir sessions tablosunda staging'de Index Scan alan sorgu production'da neden Seq Scan'e düşüyor, EXPLAIN ile nasıl teşhis ederim?](https://www.muhammetsafak.com.tr/sor-bakalim/surekli-guncellenen-bir-sessions-tablosunda-stagingde-index-scan-alan-sorgu-productionda/) — Sor Bakalım
