# 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; gereksiz user_id indeksini silin.

- Soruldu: 2026-05-17
- Yanıtlandı: 2026-05-20
- Soran: Doğa
- Etiketler: performans, postgresql, veritabani
- Kaynak: https://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.

## Kısa cevap

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ı. Aynı "sıcak tabloyu hangi kolona göre düzenlemeli" sorusunun kiracı bazlı hâlini [multi-tenant izolasyon kaydında](/tr/sor-bakalim/multi-tenant-saas-veritabani-izolasyon-stratejisi/) ele almıştım.

## Neden

1. **Eşitlik kolonları önde, sıralama kolonu sonda olmalı.** B-tree, eşitlik filtresini uyguladıktan sonra satırları zaten sıralı verir; ayrı bir Sort adımına gerek kalmaz. Sıra bozulursa planlayıcı sıralamayı kendisi yapmak zorunda kalır.

2. **Cardinality burada ikincil.** Her iki kolon da eşitlik predicate'i olduğu için asıl kazanç sıralamayı index'e taşımakta; eşitlik kolonları arasında sıra, index'in ne kadarının taranacağını değiştirmez (PostgreSQL kılavuzu: baştaki kolonlardaki eşitlik koşulları taranan bölümü sınırlar); `user_id` başa gidiyor çünkü yalnızca `user_id` ile filtreleyen sorgulara da hizmet ediyor.

3. **Fazlalık index bedava değil.** Tekli `user_id` indeksi artık composite'in gereksiz bir öneki, ama her yazmada hâlâ güncelleniyor.

## Ne yapmalı

1. **`(user_id, status, created_at DESC)` composite index'ini kur.** Eşitlik kolonları başta, ORDER BY kolonu sonda.

2. **`DESC`'i index tanımına yazmak burada isteğe bağlı.** PostgreSQL bir B-tree'yi geriye doğru tarayabilir; baştaki iki kolon eşitlikle sabitlendiğinde `(user_id, status, created_at)` de `ORDER BY created_at DESC`'i karşılar. Index'in sorguyu belgelemesini istiyorsan yönü yaz; ORDER BY yönleri karışıksa (ör. `x ASC, y DESC`) gerçekten gerekir.

3. **`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ına bak: sıralama kolonu eşitlik kolonlarından sonra gelmeli.

4. **Tekli `user_id` indeksini sil.** Composite, baştaki kolon üzerindeki her aramayı zaten karşılıyor. `status` ve `created_at` indeksleri composite'in öneki değil; başka sorgular kullanıyorsa tut, silmeden önce `pg_stat_user_indexes`'e bak.

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 gereksiz kalan `user_id` indeksini silerdim. 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
- [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
- [Sadece 'pending' satırları taranan bir kuyruk tablosunda partial index kullanmalı mıyım?](https://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://muhammetsafak.com/tr/sor-bakalim/pgbouncer-session-transaction-statement-modu-ve-prepared-statement/) — Sor Bakalım
