İki karşıt ekip gördüm. Biri tabloya hiç indeks koymamıştı — her sorgu bir sequential scan, her liste sayfası saniyeler sürüyordu. Diğeri tam tersini yapmıştı: “ne olur ne olmaz” diye her kolona bir indeks açmıştı ve şimdi her INSERT ağır aksak ilerliyordu.

İkisi de aynı yanılgının iki ucu: indeksi ücretsiz bir hız kaynağı sanmak.

İndeks ücretsiz hız değildir

Bir indeks okumayı hızlandırır, çünkü veritabanı tüm tabloyu taramak yerine sıralı bir yapıya bakar. Ama o sıralı yapı kendi kendine güncel kalmaz: tablodaki bir INSERT, o tablonun her indeksine bir girdi yazar; bir UPDATE de indeksli bir kolona dokunduğu anda aynısını yapar. İstisnalar var — indeksli hiçbir kolonu değiştirmeyen HOT güncellemesi indekslere dokunmaz, kısmi indeks yüklemi dışında kalan satırlar için güncellenmez, DELETE ise indeks girdilerini temizlemeyi VACUUM’a bırakır — ama bunlar istisna, varsayılan değil.

Yani her indeks bir takastır: okuma hızını, yazma maliyetiyle satın alırsınız. Beş indeksli bir tabloya tek satır eklemek, altı yapıyı birden güncellemektir. İndeks “bedava okuma hızı” değil, “yazmayla ödenmiş okuma hızı”dır.

Eksik indeks: tahmin değil, ölçüm

Eksik indeksi sorgu planından bulun, tahminden değil:

EXPLAIN ANALYZE
SELECT * FROM orders WHERE status = 'pending';

Çıktıda büyük bir tabloda Seq Scan görüyorsanız ve sorgu satırların küçük bir kısmını döndürüyorsa, muhtemelen bir indeks eksiktir. pg_stat_user_tables tablosunda seq_scan sayısı yüksek olan büyük tabloları tarayın — bunlar adaylarınızdır.

İndeksi gerçek, yavaş bir sorgu için ekleyin. “İleride lazım olabilir” diye eklenen indeks, spekülatif genelliğin veritabanı katmanındaki hâlidir.

Fazla indeks: kullanılmayanı bul

Ters yönde, hiç kullanılmayan indeksler her yazmayı boş yere pahalılaştırır. Onları da ölçümle bulun:

SELECT indexrelname, idx_scan
FROM pg_stat_user_indexes
WHERE idx_scan = 0;

idx_scan = 0 olan bir indeks hiç okuma hızlandırmıyor ama her yazmaya maliyet ekliyor demektir. (Primary key ve unique kısıtların indekslerini elbette koruyun.) Bu, veri yoğunluklu sistemlerin yazma kırılma noktasında sayılan ilk temizliktir.

Doğru indeksi seçmek

İndeks “var/yok”tan ibaret değildir; doğru türü ve şekli seçmek önemlidir:

  • Bileşik indekste kolon sırası. (a, b) indeksi, a’da eşitlik + b’de aralık sorgusuna hizmet eder; b’de tek başına yapılan bir arama da bu indeksi kullanabilir ama taranan indeks bölümünü daraltmaz — PostgreSQL 18 öncesinde indeksin tamamı taranır, 18’den itibaren skip scan optimizasyonu bunu daraltır. Eşitlikle filtrelenen kolonu başa, aralıkla filtreleneni sona koyun.
  • Kısmi indeks (partial). Sorgu hep aynı alt kümeye bakıyorsa indeksi de o alt kümeyle sınırlayın: CREATE INDEX ... WHERE status = 'active'. Daha küçük indeks, daha ucuz bakım.
  • Covering indeks. INCLUDE ile sık okunan kolonları indekse ekleyip veritabanının tabloya hiç gitmemesini sağlayabilirsiniz.
  • B-tree dışı türler. Eşitlik/sıralama için B-tree; jsonb ve tam metin için GIN; saf sıralı, append ağırlıklı veri için küçük ve ucuz BRIN.

Duplikat ve örtüşen indeksler

(a) üzerindeki bir indeks, zaten (a, b) indeksiniz varsa gereksizdir — bileşik indeks a ile başlayan sorgulara da hizmet eder. Bu tür örtüşmeleri periyodik olarak temizleyin; her biri sessiz bir yazma vergisidir.

Denge sorgu planıyla kurulur

Tek kural: indeksi ne tahminle ekleyin ne tahminle silin. Bir indeks, gerçek bir sorgu planı onu istediği için eklenir; bir indeks, istatistikler onu kimsenin kullanmadığını gösterdiği için silinir. Eksik ile fazla arasındaki denge, hislerle değil, EXPLAIN ANALYZE ve pg_stat_user_indexes ile kurulur.


İndeks yönetimi, “daha çok indeks daha iyi” sanısından kurtulmakla başlar. Her indeks bir okuma kazancı ve bir yazma maliyetidir; iyi yönetim, bu ikisini ölçerek dengede tutmaktır.

Eksik indeks sorguyu yavaşlatır; fazlası ise tüm tabloyu. İkisinden de ölçüm korur.