// Backend 2026-07-227 min

Veritabanı İndeksi Nedir? Yavaş Bir Sorguyu Saniyelerden Milisaniyeye İndiren Şey

DatabasePerformanceBackendSQL

Aynı tabloda bir sorgu neden 8 saniye sürer, bir başkası 10 milisaniyede biter? Veritabanı indeksinin kapak altında gerçekte ne yaptığını anlatıyorum.

Bir SaaS panelinin “Siparişler” sayfası açılıyor ve yükleme ikonu 6-7 saniye dönüp duruyor. Kullanıcı sayfayı yeniliyor, bağlantısını kontrol ediyor, sorun hâlâ orada. Geliştirici log'a bakıyor ve arkada tek bir SQL sorgusu görüyor: `SELECT * FROM orders WHERE customer_id = 482913 ORDER BY created_at DESC LIMIT 20`. Basit görünen bu sorgu, 2 milyon satırlık bir tabloda saniyelerce sürüyor. Tek satırlık bir komut çalıştırılıyor — `CREATE INDEX idx_orders_customer ON orders(customer_id, created_at);` — ve aynı sorgu 9 milisaniyeye düşüyor. Bu an, “veritabanı indeksi nedir” sorusunun en somut cevabı: index, veritabanının bir soruyu yanıtlamak için kaç satıra bakması gerektiğini kökten değiştiren bir yapı. Bu yazıda index'in gerçekte ne yaptığını, bu farkın nereden geldiğini, ne zaman eklenmesi gerektiğini ve bedelsiz olmadığı yerleri gerçek bir senaryo üzerinden anlatıyorum.

##Veritabanı İndeksi Nedir, Gerçekte Ne Yapar?

Bir kitabın sonundaki dizini düşün. “Kubilay Han” geçen her sayfayı bulmak için kitabı baştan sona okumak zorunda değilsin; dizine gidip “K” harfinde “Kubilay Han: sayfa 214, 389” yazdığını görüp direkt oraya atlarsın. Veritabanı indeksi de tam olarak bunu yapar: tablodaki bir veya birkaç sütunun değerlerini önceden sıralı bir yapıda (genelde bir B-tree) tutar ve her değeri, o değere sahip satırların diskteki gerçek konumuna işaret eden bir işaretçiyle eşler. `customer_id` sütununa index koyduğunda, veritabanı motoru “482913” değerine sahip satırları bulmak için artık tüm tabloyu taramaz; sıralı yapıda ikili aramaya benzer bir mantıkla doğrudan doğru dala iner ve işaretçiyi takip eder. Index olmadan motorun elinde tek bir yol var: her satırı sırayla okuyup koşula uyup uymadığına bakmak. Buna “full table scan” denir ve tablo büyüdükçe doğrusal olarak yavaşlar.

##Bu Hız Farkı Nereden Geliyor? Kapağın Altında Olan

Full table scan'in maliyeti tablodaki satır sayısıyla doğru orantılı büyür: 10 bin satırda gözle görülmez, 2 milyon satırda saniyelerle ölçülür, 50 milyon satırda dakikalara çıkabilir. Index'li bir arama ise B-tree'nin yüksekliğiyle orantılı büyür — ki bu, satır sayısı katlanarak artsa da çok az artar. Pratikte bu, 2 milyon satırlık bir tabloda index'siz sorgunun her satıra bakması, index'li sorgunun ise sadece birkaç düzine karşılaştırma yapıp doğru satıra inmesi demek. Bunun yanında index, veriyi sıralı tuttuğu için `ORDER BY created_at DESC` gibi sıralama işlemlerini de bedavaya getirir — motor sonucu ayrıca sıralamak zorunda kalmaz, çünkü index zaten o sırada. Aynı mantık `JOIN` işlemlerinde de geçerli: iki tabloyu birleştirirken eşleşen satırı aramak, index varsa anında, yoksa her satır için tam tarama gerektirir.

##Gerçek Bir Senaryo: 2 Milyon Satırlık Sipariş Tablosu

Örneği somutlaştıralım: üç yıldır büyüyen bir e-ticaret şirketinin “orders” tablosu var, 2,1 milyon satır. Müşteri hizmetleri ekibi, bir müşterinin son siparişlerine bakmak için admin panelinde “Müşteri Geçmişi” sekmesini açıyor. Bu sayfa arka planda `customer_id`'ye göre filtreleyip `created_at`'e göre sıralayan bir sorgu çalıştırıyor. Şirket büyüdükçe bu sayfa yavaşlamaya başlıyor — önce 1-2 saniye, sonra 4-5 saniye, sonunda destek ekibi “panel donuyor” diye şikayet etmeye başlıyor. Geliştirici `EXPLAIN ANALYZE` ile sorguyu incelediğinde planın “Seq Scan on orders” dediğini görüyor: veritabanı her seferinde 2,1 milyon satırın tamamını tarıyor, gerçek çalışma süresi ortalama 900ms-2,5sn arasında değişiyor — sunucunun o anki yüküne göre.

Çözüm, `(customer_id, created_at)` üzerinde bileşik (composite) bir index eklemek: `CREATE INDEX idx_orders_customer_created ON orders(customer_id, created_at DESC);`. Bu index, sütun sırasını bilerek seçiliyor — önce `customer_id`'ye göre daralt, sonra o müşteri için zaten `created_at` sırasına göre tutulan satırları oku. Index oluşturulduktan sonra aynı `EXPLAIN ANALYZE` planı “Index Scan using idx_orders_customer_created” gösteriyor ve gerçek çalışma süresi 4-12 milisaniyeye düşüyor. Destek ekibi için değişen tek şey, sayfanın artık anında açılması; arka planda değişen şey, veritabanı motorunun 2,1 milyon satırı taramak yerine doğrudan doğru dallanmaya inmesi. Bu, küçük görünen bir SQL komutunun operasyonel bir şikayeti tamamen ortadan kaldırdığı, gerçek ve sık rastlanan bir senaryo.

>Hangi Sütunlar İndex İçin İyi Aday?

  • WHERE koşullarında sık kullanılan sütunlar (örn. customer_id, status, email)
  • ORDER BY veya GROUP BY'da sık geçen sütunlar (örn. created_at)
  • JOIN'lerde eşleştirme yapılan foreign key sütunları
  • Yüksek kardinaliteli sütunlar — yani çok farklı değer alabilen sütunlar (email iyi bir aday, is_active gibi iki değerli bir sütun kötü bir aday)
  • Sık sorgulanan ama nadiren güncellenen sütunlar — index'in en verimli olduğu yer

Aynı mantık çok daha sık karşılaşılan bir yerde de geçerli: kullanıcı girişi. Bir “users” tablosunda `email` sütununa index yoksa, her giriş denemesinde veritabanı tüm kullanıcı satırlarını tarayıp eşleşen e-postayı arar — 50 bin kullanıcıda bu gecikme fark edilmeyebilir, 5 milyon kullanıcıda login sayfası gözle görülür yavaşlar. `email` üzerine tekil (unique) bir index koymak hem bu aramayı anında hızlandırır hem de aynı e-postanın iki kez kaydolmasını veritabanı seviyesinde engeller — index bazen sadece hız değil, veri bütünlüğü garantisi de sağlar.

##Index Bedelsiz Değil: Nelere Dikkat Etmeli

Index okuma hızını artırırken yazma maliyetini yükseltir. Her `INSERT`, `UPDATE` veya `DELETE` işleminde veritabanı motoru sadece tabloyu değil, o tablodaki her index'i de güncellemek zorunda. Bir tabloda 8-10 index varsa, tek bir satır eklemek 8-10 ayrı yapıyı güncellemek anlamına gelir — bu da yazma işlemlerini yavaşlatır ve disk kullanımını artırır (bir index, tablonun kendisi kadar yer kaplayabilir). Bu yüzden “her sütuna index koyalım, kötü bir şey olmaz” yaklaşımı yanlış: yoğun yazma yapılan ama az sorgulanan bir tabloda gereksiz index'ler net performans kaybı yaratır.

Üç pratik tuzak var. Birincisi, bileşik index'lerde sütun sırası önemli: `(customer_id, created_at)` index'i `WHERE created_at > X` sorgusuna neredeyse hiç yardım etmez, çünkü index önce `customer_id`'ye göre sıralı — sırayı ters kurmak gerekir. İkincisi, `LIKE '%kelime%'` gibi baştan joker karakterli aramalar standart bir B-tree index'i kullanamaz, çünkü index sıralı bir yapı ve “ortasında geçen” bir kalıp bu yapıda aranamaz; bu durumda full-text search veya trigram index gibi farklı bir yapı gerekir. Üçüncüsü, bir sütuna fonksiyon uygulanmış sorgular (`WHERE LOWER(email) = 'x'`) düz index'i atlayıp yine full scan'e düşebilir — çözüm, o ifadenin kendisi üzerine bir “expression index” kurmak. Bunların hiçbiri egzotik değil; her biri gerçek projelerde sık karşılaşılan, `EXPLAIN ANALYZE` ile birkaç dakikada teşhis edilebilen durumlar.

Bu üç tuzağı düzenli olarak yakalamanın en pratik yolu, üretim ortamında ayda bir en yavaş 10-15 sorgunun `EXPLAIN ANALYZE` çıktısını gözden geçirmek ve veritabanının kendi index kullanım istatistiklerine (PostgreSQL'de `pg_stat_user_indexes`, MySQL'de `sys.schema_unused_indexes`) bakmaktır — çoğu zaman kullanılmayan bir index'i silmek, yeni bir index eklemekten daha büyük bir performans kazancı sağlar.

Full table scan (2M+ satır)

800ms – 3sn

Doğru index ile aynı sorgu

1 – 15ms

Yazma işlemine tipik ek yük (index başına)

%5 – 15

##Sık Sorulan Sorular

>Her sütuna index eklemeli miyim?

Hayır. Sadece `WHERE`, `JOIN` veya `ORDER BY` içinde sık kullanılan sütunlara index ekle. Nadiren sorgulanan veya çok az farklı değer alan (örneğin bir `boolean` sütun) sütunlara index koymak, yazma performansını düşürür ve disk kullanımını artırırken sorgu hızına anlamlı bir katkı sağlamaz. Kural olarak: önce gerçek sorgu loglarına bak, en sık ve en yavaş çalışan sorguları bul, sonra o sorguların filtrelediği/sıraladığı sütunlara index ekle — tahminle değil, ölçerek karar ver.

>Index eklemek canlı bir uygulamayı durdurur mu?

Genelde hayır, ama büyük tablolarda dikkat gerekir. PostgreSQL'de `CREATE INDEX CONCURRENTLY` tabloyu kilitlemeden index oluşturur (biraz daha uzun sürer ama uygulama çalışırken yapılabilir); MySQL'de InnoDB'nin çoğu index oluşturma işlemi de online çalışır. Yine de çok büyük tablolarda (on milyonlarca satır) işlemin dakikalarca sürebileceğini ve sunucu yükünü artırabileceğini hesaba katıp, mümkünse düşük trafikli bir saatte yapmak daha güvenli.

>Index eklememe rağmen sorgu hâlâ yavaş, neden?

En sık üç sebep: sütun sırası yanlış kurulmuş bileşik index, sorgu bir fonksiyon veya tip dönüşümü uyguladığı için index'i atlıyor, veya ORM'in ürettiği SQL beklediğinden farklı bir sorgu çalıştırıyor. Tahmin etmek yerine `EXPLAIN ANALYZE` (PostgreSQL/MySQL) çalıştırıp gerçek sorgu planına bak — plan “Index Scan” değil “Seq Scan” diyorsa, index kullanılmıyor demektir ve sebebini plan çıktısı genelde açıkça gösterir.

>Bir tabloda kaç index çok sayılır?

Sabit bir sayı yok; okuma/yazma oranına bağlı. Ağırlıklı okunan, nadiren yazılan bir raporlama tablosunda 10+ index makul olabilir; sürekli yazma alan bir işlem tablosunda (örneğin sipariş satırları) 3-5 index sınırını zorlar, her yeni index'in yazma maliyetini gerçekten hak edip etmediğini sorgulamak gerekir. Kullanılmayan index'leri düzenli olarak temizlemek (çoğu veritabanı motoru “index kullanım istatistiği” tutar) de aynı derecede önemli bir bakım işi.

>Index, COUNT(*) veya arama (search) özelliklerini de hızlandırır mı?

Kısmen. Bir `WHERE` koşuluyla filtrelenen `COUNT(*)` sorgusu, o koşuldaki sütun index'liyse gerçekten hızlanır — motor eşleşen satırları index üzerinden sayar. Ama “ürün adı içinde X geçen” gibi serbest metin araması standart bir index ile iyi çalışmaz; bunun için PostgreSQL'de `tsvector` tabanlı full-text search index'i veya Elasticsearch gibi ayrı bir arama motoru daha doğru araçtır. Yani index her hız sorununun tek çözümü değil — hangi sorgu türüne hangi yapının uygun olduğunu bilmek, doğru index'i seçmek kadar önemli.

Index'in özünde yaptığı şey basit: veritabanına “bu soruyu her seferinde nasıl daha az iş yaparak yanıtlayabilirsin” demek. Bir sorgu yavaşladığında ilk bakılacak yer neredeyse her zaman burası — mimariyi değiştirmeden, sunucuyu büyütmeden, sadece doğru yapıyı ekleyerek saniyelik bir gecikmeyi milisaniyeye indirebiliyorsun. Bir projede benzer bir performans sorununu incelemek istersen /contact üzerinden yazabilirsin; SaaS ve otomasyon projelerinde nasıl çalıştığımı görmek istersen /services ve /process-pricing sayfalarına da göz atabilirsin.

// BİRLİKTE ÇALIŞALIM

Benzer bir SaaS projesi mi planlıyorsun?

Kapsam, MVP sıralaması ve teslim takvimi için birlikte net bir yol haritası çıkarabiliriz.

> İLETİŞİME GEÇ