2026-08-07veri3 dk

İndeks eklediniz ama sorgu hâlâ yavaş: indeksin işe yaramadığı durumlar

İndeks var, sorgu yavaş. Bunun sebebi genelde indeksin eksikliği değil, sorgunun indeksi kullanılamaz hâle getirmesi. En sık altı durum.

Yavaş sorguya verilen ilk tepki genelde indeks eklemek oluyor. İndeks ekleniyor, sorgu hâlâ yavaş, ikinci indeks ekleniyor. Sonuçta tablo indekslerle doluyor, yazma performansı düşüyor ve okuma hâlâ yavaş.

Sorun neredeyse her zaman şu: indeks var ama sorgu onu kullanamıyor. İşte en sık karşılaşılan altı durum.

1. Sütun bir fonksiyonun içinde

-- indeks kullanılamaz
WHERE LOWER(eposta) = 'ali@ornek.com'

-- indeks kullanılabilir
WHERE eposta = 'ali@ornek.com'

Sütuna bir fonksiyon uygulandığı anda, indeksteki değerler artık aranan değerle karşılaştırılamıyor. Çözüm ya sorguyu düzeltmek ya da ifade indeksi oluşturmak:

CREATE INDEX ON kullanicilar (LOWER(eposta));

Aynı tuzak tarihlerde çok yaygın:

-- indeks kullanılamaz
WHERE DATE(olusturma_zamani) = '2026-08-01'

-- indeks kullanılabilir
WHERE olusturma_zamani >= '2026-08-01'
  AND olusturma_zamani <  '2026-08-02'

2. Tip uyuşmazlığı

Sütun varchar, parametre sayı olarak gönderiliyor. Veritabanı örtük dönüşüm yapıyor ve indeks devre dışı kalıyor. Bu, ORM kullanan projelerde sinsi bir şekilde oluyor çünkü sorgu koda bakınca doğru görünüyor.

EXPLAIN çıktısında bir dönüşüm (cast) görüyorsanız sebep budur.

3. Bileşik indekste sütun sırası yanlış

(sirket_id, durum, olusturma_zamani) şeklinde bir indeks;

  • WHERE sirket_id = 5 → kullanılır
  • WHERE sirket_id = 5 AND durum = 'aktif' → kullanılır
  • WHERE durum = 'aktif' → kullanılmaz

Bileşik indeks, soldan başlayan bir önek gibi çalışır. Sıralamayı, sorgularınızda en sık ve en seçici olan sütun başta olacak şekilde kurun.

Sıralama (ORDER BY) da aynı indeksten faydalanabilir — ama yalnızca yön ve sıra uyuşuyorsa.

4. Seçicilik düşük

Bir sütunun yalnızca iki değeri varsa (aktif/pasif) ve kayıtların %60'ı aktif ise, veritabanı indeksi kullanmak yerine tabloyu baştan taramayı tercih eder — ve haklıdır. İndeks üzerinden gidip her satır için tabloya dönmek, sıralı okumadan pahalıdır.

Bu durumda çözüm kısmi indeks:

CREATE INDEX ON siparisler (olusturma_zamani)
WHERE durum = 'bekliyor';

Az sayıda "bekliyor" kaydı varsa bu indeks hem küçük hem çok etkili olur.

5. OFFSET ile sayfalama

SELECT * FROM yazilar ORDER BY id DESC LIMIT 20 OFFSET 100000;

Bu sorgu 100.020 satır okuyup 100.000'ini atar. Sayfa numarası büyüdükçe doğrusal olarak yavaşlar ve hiçbir indeks bunu kurtarmaz.

Çözüm, imleç (keyset) sayfalama:

SELECT * FROM yazilar WHERE id < :son_gorulen_id ORDER BY id DESC LIMIT 20;

Sayfa numarası göstermeniz gerekmiyorsa — ki çoğu arayüzde gerekmiyor — bu her zaman daha iyi.

6. İstatistikler bayat

Sorgu planlayıcı, tablo hakkındaki istatistiklere göre karar verir. Toplu veri yüklemesinden sonra istatistikler güncellenmediyse planlayıcı yanlış karar verir. ANALYZE çalıştırmak, bazen saatlerce aranan sorunu bir saniyede çözer.

Yöntem: önce ölç

İndeks eklemeden önce her zaman planı okuyun:

EXPLAIN (ANALYZE, BUFFERS) SELECT ...;

Bakılacak üç şey:

  1. Seq Scan mı Index Scan mı? Küçük tablolarda Seq Scan normaldir, panik yapmayın.
  2. Tahmini satır sayısı ile gerçek satır sayısı arasındaki fark. Büyük farklar bayat istatistiğe işaret eder.
  3. En çok zamanı hangi düğüm harcıyor? Toplam süreyi değil, dağılımı okuyun.

İndeksin bedeli

İndeks bedava değil: her INSERT, UPDATE, DELETE işleminde güncellenmesi gerekiyor ve disk yer kaplıyor. Kullanılmayan indeksleri tespit edip silmek, çoğu olgun projede yazma performansını gözle görülür şekilde iyileştiriyor.

PostgreSQL'de kullanılmayan indeksleri pg_stat_user_indexes görünümünden bulabilirsiniz — idx_scan değeri sıfıra yakın olanlar aday.

Özet

Yavaş sorgu gördüğünüzde sıralama şu olsun: önce planı oku, sonra sorguyu düzelt, en son indeks ekle. Tersten gidildiğinde tablo indeksle dolar ama sorgu yavaş kalır.