SQL PERFORMANS TEMELLERİ: İNDEKS MANTIĞI VE SORGU OPTİMİZASYONU
Bir sorgu “doğru” sonuç veriyor olabilir; ama üretim ortamında fark yaratan, bu sonuca ne kadar hızlı ve ne kadar tutarlı ulaştığınızdır. SQL performansı, sadece CPU ya da disk gücüyle değil; veri modeli, indeks seçimi, sorgu yazım kalitesi ve istatistik doğruluğuyla birlikte şekillenir.
Bu makalede, “neden yavaş?” sorusunu sistematik biçimde yanıtlayacak bir temel kurgu kuracağız. İndekslerin nasıl çalıştığını, yürütme planında hangi ipuçlarının kritik olduğunu ve sorgu optimizasyonunda en çok kazanç getiren teknikleri gerçekçi örneklerle ele alacağız.
Hedefimiz sihirli bir “tek ayar” değil; ölçülebilir, tekrarlanabilir ve ekipçe sürdürülebilir bir performans yaklaşımı. Eğer kurum içinde SQL’e dayanan uygulamalar geliştiriyor veya kritik raporlama/analitik iş yüklerini yönetiyorsanız, aşağıdaki adımlar performans sorunlarını çözmede sağlam bir çerçeve sunar.

Primary Keyword: SQL performans temelleri neden önemlidir?
SQL performans temelleri, ölçek büyüdükçe “küçük” görünen gecikmelerin nasıl çarpan etkisi yarattığını anlamanızı sağlar. 50 ms süren bir sorgu, saniyede yüzlerce kez çalışıyorsa toplam CPU tüketimi ve kilitlenme baskısı dramatik şekilde artar. Benzer biçimde, yanlış bir indeks tasarımı disk IO’yu artırır, buffer pool’u kirletir ve gecikmeleri dalgalı hale getirir.
Kurumsal sistemlerde performans aynı zamanda bir maliyet konusudur: daha fazla donanım, daha büyük lisans, daha fazla operasyonel emek. Doğru optimizasyon ise çoğu zaman mevcut kaynaklarla daha yüksek throughput ve daha düşük yanıt süresi sağlar.
Performans metriği: sadece süre değil, tutarlılık
Bir sorgunun ortalama süresi düşük olabilir; ancak p95/p99 gecikmesi yüksekse kullanıcı deneyimi bozulur. Bu yüzden performansı değerlendirirken ortalama yanında kuyruk gecikmelerine ve tekrarlanabilirliğe odaklanın. Parametre koklama, istatistik sapması veya bellek baskısı gibi unsurlar aynı sorgunun bazen çok hızlı bazen çok yavaş çalışmasına neden olabilir.
Ölçmeden optimize edilmez: temel gözlem noktaları
İyileştirmeye başlamadan önce “nerede zaman harcıyor?” sorusunu ölçmek gerekir. Yürütme planı, IO istatistikleri, bekleme türleri, kilitlenme grafikleri ve query store/performans kayıtları en sık başvurulan veri kaynaklarıdır. Bu veriler olmadan yapılan değişiklikler tesadüfi olur ve regressions (geri gidiş) riskini büyütür.
İndeks mantığı: B-Tree yapısı, seek/scan ve seçicilik
Çoğu ilişkisel veritabanında (özellikle B-Tree tabanlı sistemlerde) indeks, anahtar değerler üzerinden sıralı bir yapı sunarak aranan satırlara daha az sayfa okumasıyla ulaşmayı hedefler. Ancak indeksin performans kazancı, aranan koşulun seçiciliğine (selectivity), veri dağılımına ve sorgunun indeksle ne kadar uyumlu yazıldığına bağlıdır.
Index seek ile index scan arasındaki fark
Index seek, indeks içinde aranan aralığa doğrudan sıçrayarak az sayıda sayfa okur. Index scan ise daha geniş bir kısmı (bazen tamamını) tarar. Scan her zaman “kötü” değildir; küçük tabloda veya geniş aralık sorgularında scan mantıklı olabilir. Kritik nokta, beklenen IO ile gerçek IO’nun uyumudur.
Seçicilik ve kardinalite: optimizer’ın tahminleri
Query optimizer, indeks seçimini ve join stratejisini kardinalite tahminleriyle yapar. Tahminler kötüleştiğinde yanlış join sırası, gereksiz spool, aşırı bellek ayırma veya yetersiz bellek nedeniyle spill-to-disk görülebilir. Bu yüzden istatistikler ve veri dağılımı performansın çekirdeğidir.
Covering index ve key lookup maliyeti
Bir sorgu, filtreyi indeksle hızlıca bulsa bile seçilen kolonlar indeks üzerinde yoksa tabloya geri dönerek ek okumalar yapabilir. Bu durum birçok sistemde “key lookup” olarak görünür ve yüksek satır sayılarında ciddi maliyet üretir. Doğru kurgulanmış covering index, seçilen kolonları indeks üzerinde taşıyarak bu maliyeti azaltır; ancak indeksin boyutu ve yazma maliyetini de artırabileceği unutulmamalıdır.

Sargable koşullar: fonksiyonlar, dönüşümler ve doğru filtre yazımı
Birçok performans problemi, filtre koşulunun indeksi “kullanılamaz” hale getirmesiyle başlar. Sargable (search-argument able) koşullar, indeks anahtarını doğrudan karşılaştırmaya izin veren koşullardır. Aksi durumda optimizer çoğu zaman scan’e yönelir.
Fonksiyon kullanımı indeksi devre dışı bırakabilir
Kolonun üzerinde fonksiyon çalıştırmak (ör. UPPER(col), DATE(col), CAST(col)) indeksin arama yeteneğini azaltabilir. Bunun yerine koşulu kolona değil, parametreye/konstanta uygulamak veya hesaplanmış kolon (computed column) + indeks yaklaşımını düşünmek gerekir.
Aralık sorguları ve tarih filtreleri
Tarih filtrelerinde “günün tarihi” gibi dönüşümler yerine, yarı açık aralık (half-open interval) kullanmak hem doğruluk hem performans sağlar. Örneğin >= ve < kombinasyonu, indeks aramalarını daha tutarlı kılar ve saat/dakika bileşenleri yüzünden kaçırma riskini azaltır.
-- Kötü örnek: kolon üzerinde dönüşüm
SELECT o.OrderId, o.CustomerId, o.OrderDate
FROM Orders o
WHERE CAST(o.OrderDate AS DATE) = @targetDate;
-- Daha iyi: yarı açık aralık ve sargable koşul
SELECT o.OrderId, o.CustomerId, o.OrderDate
FROM Orders o
WHERE o.OrderDate >= @targetDate
AND o.OrderDate < DATEADD(day, 1, @targetDate);Bu yaklaşım, özellikle büyük tablolar ve yoğun raporlama sorgularında IO’yu belirgin şekilde düşürür. Ayrıca parametre değerleri değiştikçe plan stabilitesinin artmasına katkı sağlar.
Yürütme planı okuma: operatörler, maliyetler ve uyarı işaretleri
Yürütme planı, optimizer’ın seçtiği yolun haritasıdır. Planı “tek bakışta” okuyabilmek, sorun çözme süresini ciddi biçimde kısaltır. Ancak plan maliyeti her zaman gerçek süreyle birebir örtüşmez; bu yüzden planı IO ve runtime istatistikleriyle birlikte değerlendirmek gerekir.
Join stratejileri: nested loops, hash join, merge join
Join tipi, satır sayıları ve indeks varlığına göre seçilir. Küçük bir dış tablo ve uygun indeks varsa nested loops verimli olur. Büyük setlerde hash join avantajlı olabilir; fakat yeterli bellek yoksa hash spill oluşur ve disk IO artar. Merge join ise sıralı giriş ister; doğru sıralama/indeksle çok hızlı olabilir, aksi halde sorting maliyeti yükselebilir.
Sort, spool ve spill: planın gizli maliyetleri
Plan üzerinde “Sort” veya “Spool” operatörleri sıkça darboğaz sinyali verir. Sort genellikle uygun indeks yokluğunu veya ORDER BY/GROUP BY ihtiyacını gösterir. Spool ise yeniden kullanım veya tekrar eden erişim optimizasyonu olabilir; ama yanlış tahminle gereksiz spool görülebilir. “Spill to tempdb/disk” uyarıları bellek yetersizliğini veya aşırı geniş satırları işaret eder.
Parametre koklama ve plan stabilitesi
Aynı sorgu farklı parametrelerle çalıştığında veri dağılımı değişebilir. Bir parametre değeri çok az satır döndürürken diğeri milyonlarca satır döndürebilir. Bu durumda bir kez derlenen plan, tüm parametreler için ideal olmayabilir. Çözüm seçenekleri arasında yeniden derleme stratejisi, plan guide yaklaşımı, daha iyi istatistikler veya sorguyu bölmek (ör. farklı senaryolar için farklı yollar) yer alır. Burada amaç, rastgele “hint” eklemek değil; tutarlı bir davranış elde etmektir.
İndeks tasarımı: doğru kolon sırası, filtreli indeks ve bakım stratejisi
İndeks eklemek çoğu zaman ilk refleks olur; ancak gelişi güzel indeksler yazma performansını düşürür, bakım maliyetini artırır ve optimizer’ı daha karmaşık seçimlere zorlar. İyi indeks tasarımı, iş yükünü (okuma/yazma oranı), sorgu örüntülerini ve veri büyüme trendini dikkate alır.
Kolon sırası: eşitlik, aralık ve sıralama ihtiyacı
Kompozit indekslerde kolon sırası kritiktir. Genel prensip: önce eşitlik filtrelerinde kullanılan kolonlar, sonra aralık koşulları, ardından sıralama/gruplama ihtiyacını destekleyen kolonlar. Bu, indeksin daha fazla sorguda kullanılmasını sağlar ve gereksiz sort maliyetlerini azaltabilir.
Filtreli indeks ve dar kapsamlı hızlandırma
Belirli bir alt küme sürekli sorgulanıyorsa (ör. “aktif kayıtlar”), filtreli indeks yaklaşımı yüksek seçicilik sağlar. Böylece indeks boyutu küçülür, cache verimliliği artar. Ancak filtre koşulunun sorgu ile birebir örtüşmesi gerekir; aksi halde optimizer kullanmayabilir.
İndeks fragmentasyonu ve istatistik güncelleme
Yüksek yazma trafiğinde indeks sayfaları bölünebilir ve fragmentasyon artabilir. Bu her zaman felaket değildir; kritik olan, IO davranışı ve plan değişimleridir. Düzenli bakım politikası; yeniden düzenleme/yeniden oluşturma eşiği, bakım penceresi ve istatistik güncelleme stratejisini içerir. İstatistikler güncel değilse optimizer’ın tahminleri bozulur; bu da yanlış planlara ve dalgalı performansa yol açar.

Sorgu optimizasyonu: seçili kolonlar, pagination ve N+1 tuzakları
Sorgu optimizasyonu yalnızca indekse indirgenemez. Gereksiz kolon seçimi, fazla satır çekme, yanlış pagination yaklaşımı ve uygulama katmanındaki N+1 desenleri veritabanını gereksiz yere zorlar. Bu bölümde en sık görülen pratik sorunlara odaklanacağız.
SELECT * yerine ihtiyaç kadar kolon
Geniş tablolar ve büyük satırlar söz konusuysa, gereksiz kolonlar ağ trafiğini ve bellek kullanımını artırır. Ayrıca covering index ile hızlandırma şansını düşürür. İhtiyaç duyulan alanları seçmek, özellikle API endpoint’lerinde ve rapor sorgularında belirgin kazanım sağlar.
OFFSET/FETCH pagination yerine keyset pagination
Geleneksel OFFSET tabanlı pagination, sayfa numarası büyüdükçe giderek pahalılaşır; çünkü veritabanı önceki satırları atlamak için yine de işlem yapar. Keyset pagination (seek method) ise “son görülen anahtar” üzerinden ilerleyerek daha tutarlı performans sunar.
-- OFFSET tabanlı pagination (sayfa büyüdükçe maliyet artar)
SELECT OrderId, CustomerId, OrderDate
FROM Orders
ORDER BY OrderDate DESC
OFFSET @skip ROWS FETCH NEXT @take ROWS ONLY;
-- Keyset pagination (daha stabil)
SELECT TOP (@take) OrderId, CustomerId, OrderDate
FROM Orders
WHERE OrderDate < @lastSeenOrderDate
ORDER BY OrderDate DESC;Keyset yaklaşımı için ORDER BY kolonunun indekslenmesi ve tekil/kararlı sıralama anahtarının belirlenmesi önemlidir. Örneğin aynı tarih değerleri varsa ikinci bir kolonla (OrderId gibi) deterministic sıralama sağlanabilir.
N+1 sorgu deseni ve toplu çekim stratejileri
Uygulama katmanında, bir listeyi getirip her satır için ayrı sorgu atmak (N+1) veritabanında bağlantı havuzunu tüketir ve kilit/IO baskısını artırır. Çözüm; join, IN ile toplu çekim, temp table/table-valued parameter kullanımı veya batch sorgularıdır. Bu noktada amaç, “tek dev sorgu” yazmak değil; ağ tur sayısını azaltıp veritabanına daha az iş yaptırmaktır.
Eşzamanlılık: kilitler, deadlock ve izolasyon seviyeleri
Performans sorunları bazen “sorgu yavaş” değil, “sorgu bekliyor” problemidir. Kilitlenme (locking) ve bloklama (blocking), özellikle yüksek yazma trafiğinde ve uzun süren rapor sorgularında ciddi gecikme yaratır. Bu yüzden performans analizi yaparken bekleme türleri ve kilit zinciri mutlaka incelenmelidir.
Deadlock neden olur ve nasıl azaltılır?
Deadlock genellikle iki işlemin birbirinin tuttuğu kaynağı beklemesiyle oluşur. Tutarlı erişim sırası (aynı tabloları aynı sırayla güncellemek), daha kısa transaction, uygun indeksler (daha az satır kilidi), gereksiz geniş lock kapsamını azaltmak ve gerektiğinde yeniden deneme (retry) stratejisi deadlock riskini düşürür. Ayrıca rapor sorgularını üretim yazma yükünden izole etmek için okuma replikası veya snapshot tabanlı stratejiler değerlendirilebilir.
İzolasyon seviyesi: doğruluk ve performans dengesi
Daha yüksek izolasyon genellikle daha fazla kilitlenme anlamına gelir. İş gereksinimlerine göre read committed, snapshot, repeatable read gibi seçenekler değerlendirilmeli; “her şeyi serializable yapalım” gibi genellemelere kaçılmamalıdır. Doğru denge, hem veri tutarlılığı hem de throughput açısından kritiktir.
Uygulama ve operasyon: sürdürülebilir performans için kontrol listesi
Performans, tek seferlik bir proje değil; sürümlemeler, veri büyümesi ve değişen iş yüküyle sürekli yönetilmesi gereken bir disiplindir. Bu yüzden ekiplerin ortak bir kontrol listesi ve gözden geçirme ritmi oluşturması gerekir.
Pratik kontrol listesi
- Yürütme planında scan/lookup/sort/spill uyarılarını kontrol etme
- Önemli sorgular için p95/p99 gecikmelerini ve zaman içindeki trendi izleme
- İstatistik güncelliği ve veri dağılımı değişimlerini düzenli gözden geçirme
- Yeni indeks eklerken yazma maliyetini ve bakım yükünü hesaba katma
- Pagination ve API sorgularında gereksiz kolon/ satır çekimini azaltma
- Blocking/deadlock olaylarını kök neden analiziyle ele alma
Bu süreçleri standardize etmek için ekip içi pratiklere ve örnek senaryolara ihtiyaç duyarsınız. SQL tarafında daha derin bir yol haritası isterseniz SQL Eğitimi içeriği, indeks tasarımı, yürütme planı okuma ve gerçek vaka analiziyle konuyu pekiştirmenize yardımcı olur.
Özetle: iyi performans; doğru veri modeli, iyi seçilmiş indeksler, sargable sorgular, güncel istatistikler ve ölçüme dayalı bir optimizasyon döngüsünün sonucudur. Bu temelleri oturttuğunuzda, sistem büyüse bile performansı yönetilebilir hale getirirsiniz.


