Yazılarımız

Veri Akademi

SQL INDEX STRATEJİLERİ: COVERİNG INDEX, COMPOSİTE INDEX VE YAN ETKİLER

Bir sorgu 30 saniyede dönüyorken, doğru tasarlanmış bir indeksle 30 milisaniyeye inmesi çoğu zaman “sihir” gibi görünür. Oysa bu sihrin arkasında oldukça somut bir mantık var: doğru kolonları doğru sırayla indekslemek, okuma yolunu kısaltmak ve gereksiz I/O’yu azaltmak. Ancak her indeks bir kazanç değildir; yanlış indeks, yazma maliyetini artırır, plan dengesini bozar ve bakım yükünü büyütür.

Bu yazıda SQL index stratejileri odağında iki kritik yaklaşımı ele alacağız: covering index (sorguyu “tam kapsayan” indeks) ve composite index (çok kolonlu indeks). Ayrıca bu indekslerin getirdiği yan etkileri; seçicilik, istatistikler, fragmentasyon, kilitleme ve yazma yükü perspektifinden, gerçekçi örneklerle konuşacağız.

Hedefimiz “her tabloya indeks basalım” yaklaşımı değil; iş yüküne göre ölçülebilir, sürdürülebilir ve bakımı yönetilebilir bir indeks mimarisi kurmak. Daha sistematik bir yaklaşım için SQL Performance & Query Tuning Eğitimi sayfasındaki içerik akışını da inceleyebilirsiniz.

Primary hedef: SQL index stratejileri nedir, neyi optimize eder?

İndeks, veriyi belirli bir anahtara göre sıralı (veya uygun bir yapıda) tutarak arama maliyetini düşürür. Birçok senaryoda amaç, tablo taraması (scan) yerine hedefe yönelik erişim (seek) yapabilmektir. Fakat yalnızca “seek” görmek yeterli değildir; bazı durumlarda iyi bir indeks seek bile yüksek key lookup maliyetiyle toplamda pahalı kalabilir.

Strateji kelimesi burada önem kazanır: okuma ağırlıklı sistemlerde daha fazla indeks toleranslı olabilirken, yazma ağırlıklı sistemlerde indeks sayısı ve genişliği doğrudan throughput’u etkiler. Ayrıca yanlış kolon sırası, sorguların filtre ve sıralama ihtiyaçlarını karşılamaz; planlar dalgalanır, CPU ve I/O artar.

Workload odaklı düşünme: okuma mı, yazma mı?

Önce iş yükünü tanımlayın: en çok çalışan sorgular hangileri, hangi saatlerde yoğunlaşıyor, hangi tablolar sürekli güncelleniyor? Bir raporlama tablosuna agresif indeks koymak makulken, saniyede binlerce insert alan bir olay tablosunda aynı yaklaşım sorun çıkarabilir. İndeks tasarımı bir defalık iş değil; iş yükü değiştikçe indeksler de evrilmelidir.

Yürütme planı (execution plan) okuması neden belirleyici?

İndeks kararını yalnızca “bu kolonda çok arama var” diye vermek risklidir. Plan üzerinde operator maliyetleri, tahmin edilen satır sayısı ile gerçek satır sayısı farkı, lookup sayısı ve sort/hash adımları izlenmelidir. Bir indeks, sort’u kaldırıp planı sadeleştiriyorsa çoğu zaman değer üretir; ama yanlış seçicilik yüzünden index seek sonrası dev lookup’lar oluşturuyorsa toplam maliyet büyüyebilir.

Veritabanı performansında anahtar kolon sırası, seçicilik ve okuma-yazma dengesi üzerine odaklanan bir çalışma masası düzeni

Covering Index: Lookup maliyetini nasıl sıfıra yaklaştırır?

Covering index, sorgunun ihtiyaç duyduğu kolonların (filtre, join, select listesi) tamamını indeks üzerinde sunarak ek tablo erişimini azaltır. Pratikte bu, index seek sonrası “key lookup” adımını düşürmek veya tamamen ortadan kaldırmak demektir. Özellikle OLTP sistemlerinde sık tekrarlanan kısa sorgularda etkisi dramatik olabilir.

Covering yaklaşımı çoğu zaman INCLUDE edilen kolonlarla uygulanır. Anahtar kolonlar (key columns) filtre/join/sıralama için seçilir; yalnızca çıktı için gereken kolonlar INCLUDE edilir. Böylece indeks anahtarı gereksiz şişmez, yine de sorgu “kaplanmış” olur.

INCLUDE kolonları ne zaman doğru seçimdir?

SELECT listesindeki ama filtrelenmeyen kolonlar için INCLUDE genellikle doğru bir tercihtir. Örneğin bir sipariş listeleme ekranı düşünün: tarih aralığı ve müşteri ile filtreleniyor, fakat ekranda toplam tutar ve durum da gösteriliyor. Durum kolonu filtrede yoksa, onu anahtar yerine INCLUDE etmek indeks ağacını daha yönetilebilir tutar.

Covering indeksin gizli maliyeti: genişlik ve bakım

Covering indeksler “okuma”yı çok hızlandırırken “yazma”yı ağırlaştırabilir; çünkü her insert/update/delete, ilgili indeksleri de günceller. Ayrıca INCLUDE sayısı arttıkça indeks sayfası büyür, cache verimliliği düşer ve fragmentasyon ihtimali artar. Bu yüzden covering tasarımını, gerçekten yüksek frekanslı ve maliyetli sorgulara odaklayın; nadir çalışan raporlar için “her şeyi cover edelim” yaklaşımı genelde ters teper.

-- Örnek: Sık çalışan ekran sorgusunu covering index ile optimize etme
-- Senaryo: Orders tablosunda tarih aralığı ve CustomerId ile filtreleme, ekranda TotalAmount ve Status gösterimi

-- Sorgu
SELECT o.OrderId, o.OrderDate, o.TotalAmount, o.Status
FROM dbo.Orders o
WHERE o.CustomerId = @CustomerId
  AND o.OrderDate >= @StartDate
  AND o.OrderDate <  @EndDate
ORDER BY o.OrderDate DESC;

-- Öneri: Filtre ve sıralamayı destekleyen key kolonlar + çıktı kolonlarını INCLUDE
CREATE INDEX IX_Orders_CustomerId_OrderDate
ON dbo.Orders (CustomerId, OrderDate DESC)
INCLUDE (TotalAmount, Status);

Composite Index: Çok kolonlu anahtarın sırası neden her şeydir?

Composite index, birden fazla kolondan oluşan anahtar yapısıdır. Burada kritik nokta “kolon sırası”dır: indeks, en soldaki (leading) kolon(lar) üzerinden en verimli şekilde kullanılabilir. Yani (A, B, C) şeklinde bir indeks, A ile başlayan filtrelerde güçlüdür; B veya C ile tek başına başlamak ise çoğu zaman beklediğiniz kadar etkili olmaz.

Kolon sırasını belirlerken iki eksen üzerinden düşünün: seçicilik (distinct değer oranı) ve sorgu desenleri (en sık hangi filtre kombinasyonları var). Ayrıca ORDER BY veya GROUP BY ihtiyacı, kolon sırasını değiştirebilir. Bu yüzden tek bir “doğru” sıra yoktur; iş yüküne göre en büyük toplam faydayı veren sıra doğrudur.

Seçicilik ve kardinalite: “en seçici kolonu başa koy” her zaman doğru mu?

Genel öneri, daha seçici kolonları öne koymaktır; çünkü arama alanını hızlı daraltır. Fakat eğer sorgularınızın büyük kısmı A kolonu ile başlıyorsa ve A makul seçicilikteyse, A’yı başa almak toplam faydayı artırabilir. Ayrıca aralıklı filtreler (BETWEEN, >=, <) ve eşitlik filtreleri ( =) birlikte kullanıldığında, eşitlik filtrelerini öne almak çoğu zaman daha iyi sonuç verir.

Composite indeks ile sort maliyetini kaldırma

Planlarda pahalı sort adımlarını görüyorsanız, composite indeks sırası ile ORDER BY’ı desteklemek güçlü bir optimizasyondur. Özellikle “son N kayıt” senaryolarında, doğru sıralı indeks sayesinde sort olmadan top/limit ile hızlı sonuç alınabilir. Bu, hem CPU hem de tempdb baskısını düşürür.

Sorgu yürütme planında sort adımının kalktığı, seek ve düşük maliyetli operatörlerin öne çıktığı performans incelemesi

Yan etkiler: İndeks eklemek neden bazen performansı düşürür?

İndeksin “bedava hız” olmadığını kabul edince, yan etkileri yönetmek stratejinin parçası olur. İndeks eklediğinizde şu alanlarda maliyet görebilirsiniz: yazma işlemleri, kilitlenme/çekişme, bakım (rebuild/reorganize), istatistik güncellemeleri, depolama, yedekleme süresi ve replikasyon/CDC gibi altyapı bileşenlerinin yükü.

Bu etkiler çoğu zaman tek başına değil, birlikte ortaya çıkar. Örneğin geniş bir covering indeks hem daha çok sayfa taşır hem de update sırasında daha çok yazma üretir; bu da fragmentasyonu hızlandırabilir. Sonuçta sorgu hızlanırken genel sistem throughput’u düşebilir. Bu yüzden indeks kararlarını ölçümle ve izlemeyle desteklemek gerekir.

Yazma maliyeti: INSERT/UPDATE/DELETE üzerindeki çarpan etkisi

Her indeks, yazma işlemlerinde ekstra iş demektir. Bir satır insert edilirken sadece tabloya değil, ilgili tüm indeks yapılarına da kayıt eklenir. Update olan kolon indeks anahtarındaysa, satırın indeks içindeki yeri değişebilir; bu da daha maliyetli olabilir. Yüksek hacimli yazma yapılan tablolarda gereksiz indeksler “gizli fren” gibidir.

Fragmentasyon ve sayfa bölünmeleri: neyi izlemeliyim?

Özellikle artan anahtar değerleriyle (identity gibi) insert edilen tablolarda, uygun bir anahtar düzeniyle sayfa bölünmeleri azaltılabilir. Rastgele dağılımlı anahtarlar (ör. GUID) indeksin farklı bölgelerine insert yaparak daha fazla bölünme ve fragmentasyon üretebilir. Bakım stratejisi (reorganize/rebuild) ve doluluk faktörü (fill factor) birlikte düşünülmelidir.

  • İndeks genişliği arttıkça sayfa başına düşen satır azalır; buffer cache daha çabuk dolar.
  • Gereksiz indeks sayısı yazma maliyetini ve bakım penceresini büyütür.
  • Yanlış kolon sırası beklenen seek’i sağlamaz, plan “scan + lookup” gibi pahalı kombinasyonlara dönebilir.
  • İstatistik sapmaları tahmin hatalarını artırır; gerçek satır sayısı ile tahmin arasındaki fark büyür.

İndeks seçimi: SARGable koşullar, istatistikler ve plan stabilitesi

İndeks doğru olsa bile sorgu ifadeleri indeksi kullanılamaz hale getirebilir. Bunun en yaygın nedeni SARGable (Search ARGument Able) olmayan koşullardır. Örneğin tarih kolonuna fonksiyon uygulamak veya kolonu dönüşüme zorlamak (CAST/CONVERT) çoğu zaman indeks kullanımını zayıflatır. Aynı şekilde parametre tipi uyumsuzluğu da gizli dönüşümlerle planı bozabilir.

İstatistikler (statistics) ve plan seçimi arasındaki ilişki de kritiktir. Kardinalite tahminleri kötüleştiğinde, optimizer yanlış join stratejisi seçebilir veya gereksiz spool/sort adımları ekleyebilir. Bu durum bazı iş yüklerinde “bugün hızlı, yarın yavaş” şikâyetlerine yol açar.

Parametre sniffing ve plan dalgalanması

Parametre sniffing, bir sorgunun ilk çalıştırmada aldığı parametre değerine göre plan üretilip cache’lenmesi ve sonraki çalıştırmalarda da aynı planın kullanılmasıdır. Çok farklı dağılımlara sahip parametreler varsa (ör. bazı müşteriler çok büyük veri, bazıları çok küçük), tek bir plan herkese iyi gelmeyebilir. Bu durumda indeks tek başına çözüm değildir; sorgu yapısı, recompile stratejisi veya farklı yaklaşım gerekebilir.

Filtreli indeks (filtered index) ne zaman oyun değiştirir?

Eğer sorgularınız verinin küçük bir kısmını hedefliyorsa (ör. Status = 'Active' gibi), filtreli indeks müthiş bir kaldıraçtır. Daha küçük indeks, daha iyi cache kullanımı, daha az bakım ve daha hızlı seek demektir. Ancak filtre koşulu sorguyla tutarlı olmalıdır; aksi halde optimizer indeksi kullanmayabilir.

-- Örnek: SARGable olmayan koşulu düzeltme + filtreli indeks yaklaşımı
-- Problem: Fonksiyon kullanılan tarih filtresi indeksi devre dışı bırakabilir

-- Kötü (SARGable değil):
SELECT COUNT(*)
FROM dbo.Logs
WHERE CONVERT(date, CreatedAt) = @Day;

-- İyi (SARGable):
SELECT COUNT(*)
FROM dbo.Logs
WHERE CreatedAt >= @DayStart
  AND CreatedAt <  @DayEnd;

-- Eğer sadece başarılı kayıtlar sık sorgulanıyorsa:
CREATE INDEX IX_Logs_Success_CreatedAt
ON dbo.Logs (CreatedAt)
WHERE IsSuccess = 1;
Aktif kayıtların küçük bir yüzdesini hedefleyen filtreli indeks tasarımı ve seçiciliği yüksek sorgu deseni üzerinden optimizasyon akışı

Pratik karar çerçevesi: Hangi indeksi ne zaman tasarlamalı?

İndeks tasarımında en çok hata, “genel geçer” kuralları bağlamdan koparıp uygulamaktır. Bunun yerine küçük bir karar çerçevesiyle ilerlemek daha sağlıklı: önce en pahalı sorguları seçin, sonra neden pahalı olduklarını bulun (I/O, CPU, sort, join, lookup), ardından hedefe yönelik indeks tasarlayın ve ölçün.

Covering mi composite mi sorusu çoğu zaman “ikisinden biri” değil, birlikte tasarım sorusudur. Bir composite indeks, filtre ve sıralamayı destekler; covering, lookup maliyetini azaltır. İyi tasarımda anahtar kolonlar sorgu desenini taşır, INCLUDE kolonlar ise seçili çıktıyı taşır.

Kontrol listesi: indeks tasarımını yayınlamadan önce

  1. Sorgu gerçekten ilk 10/20 en pahalı sorgu arasında mı? Yoksa “hissedilen” bir problem mi?
  2. İndeks, seek sağlıyor mu ve lookup sayısını makul seviyeye indiriyor mu?
  3. Yeni indeksin yazma maliyeti ölçüldü mü (insert/update throughput, lock wait, log büyümesi)?
  4. İndeks genişliği ve bakım etkisi (fragmentasyon, rebuild süresi) değerlendirildi mi?
  5. İstatistik güncellemeleri ve plan stabilitesi izlenecek mi?

Sonuç: Hız kadar sürdürülebilirlik de hedef olmalı

İyi bir indeks stratejisi, yalnızca tek bir sorguyu hızlandırmakla kalmaz; sistemin genel kaynak kullanımını dengeler, bakım maliyetini yönetilebilir tutar ve zaman içinde iş yükü değişse bile öngörülebilir performans sağlar. Covering index ile lookup’ları azaltıp okuma yolunu kısaltabilir, composite index ile filtre ve sıralama ihtiyaçlarını tek bir yapı üzerinde karşılayabilirsiniz.

Ancak her kazancın bir bedeli olduğunu unutmayın: indeks genişliği, yazma maliyeti, fragmentasyon ve plan dalgalanması gibi yan etkileri göz ardı ederseniz, kısa vadeli hızlanma uzun vadeli yavaşlamaya dönüşebilir. Bu nedenle “tasarla → ölç → izle → sadeleştir” döngüsünü benimseyin; gerekirse daha sistematik bir yaklaşım için SQL Performance & Query Tuning Eğitimi içeriğini referans alın.

 VERİ AKADEMİ