Indexler, SQL Server performansının en temel yapı taşlarından biridir. Doğru tasarlanmış bir index sorgunun okuduğu sayfa sayısını ciddi biçimde azaltabilir; yanlış veya gereksiz indexler ise yazma maliyetini, depolama ihtiyacını ve bakım yükünü artırır. Bu yazıda indexlerin nasıl çalıştığını, hangi türün hangi ihtiyaca karşılık verdiğini ve seçeneklerin hangi dikkat noktalarıyla kullanılacağını inceliyorum.
Yazarın notu: Bu içerik, ilk olarak 16 Ağustos 2025 tarihinde Medium üzerinde yayımladığım Index Mimarisi yazısının teknik olarak gözden geçirilmiş ve genişletilmiş sürümüdür. Aşağıdaki laboratuvar ekran görüntüleri özgün çalışmadan alınmıştır.
Index neden gereklidir?
Bir kitabın içindekiler bölümü aradığımız başlığa doğrudan ulaşmamızı sağlar. Veritabanı indexi de benzer biçimde, tablonun tamamını taramak yerine aranan satırların konumuna ulaşmak için sıralı bir erişim yapısı sunar.
Northwind veritabanındaki Employees tablosu yalnızca dokuz satırken aşağıdaki sorgunun tarama yapması önemli görünmeyebilir:
SELECT *
FROM dbo.Employees;
[company_article_image src=”articles/index-architecture/01-small-table-query.png” alt=”Northwind Employees sorgusu ve küçük tablo için SQL Server istatistikleri” caption=”Dokuz satırlı başlangıç tablosunda sorgu sonucu ve ölçülen yürütme süresi.”]
Gerçek yürütme planı, tüm satırlar istendiği için Clustered Index Scan operatörünü gösterir. Küçük bir tabloda bu seçim doğal ve düşük maliyetlidir; scan görülmesi tek başına sorun olduğu anlamına gelmez.
[company_article_image src=”articles/index-architecture/02-clustered-index-scan.png” alt=”Employees tablosu Clustered Index Scan yürütme planı” caption=”Küçük veri kümesinde optimizer tarafından seçilen Clustered Index Scan.”]
Veri hacmi büyüdüğünde aynı yaklaşımın maliyeti değişir. Örnek testte FirstName filtresi için 17.054 mantıksal sayfa okuması ve paralel bir clustered index scan gözlemlendi. Uygun bir nonclustered index eklendiğinde optimizer seek planına geçerek okuma sayısını 21 sayfaya düşürdü. Bu değerler örnek ortamın veri dağılımına ve donanımına aittir; asıl ders, yalnızca süreyi değil mantıksal okumaları ve gerçek yürütme planını birlikte değerlendirmektir.
[company_article_image src=”articles/index-architecture/03-employees-row-count.png” alt=”Employees tablosunun büyütülmüş satır sayısı” caption=”Test için Employees tablosu 610.010 satıra büyütüldü.”]
[company_article_image src=”articles/index-architecture/04-filtered-query-statistics.png” alt=”Index öncesi filtreli sorgunun logical read ve süre değerleri” caption=”Index öncesinde filtreli sorgunun I/O ve zaman istatistikleri.”]
[company_article_image src=”articles/index-architecture/05-parallel-scan-plan.png” alt=”Index öncesi paralel Clustered Index Scan yürütme planı” caption=”Filtreli sorgu index öncesinde paralel scan planı kullanıyor.”]
CREATE NONCLUSTERED INDEX IX_Employees_FirstName
ON dbo.Employees (FirstName)
WITH (ONLINE = ON);
ONLINE desteği SQL Server sürümüne, edition’a ve index özelliklerine bağlıdır. Üretimde çalıştırmadan önce hedef sistemin destek matrisini ve operasyonun kilit davranışını doğrulamak gerekir.
[company_article_image src=”articles/index-architecture/06-indexed-query-statistics.png” alt=”Nonclustered index sonrasında azalan SQL Server logical read değerleri” caption=”Nonclustered index sonrasında örnek sorgu 21 mantıksal okumayla tamamlanıyor.”]
[company_article_image src=”articles/index-architecture/07-index-seek-plan.png” alt=”Nonclustered Index Seek kullanan SQL Server yürütme planı” caption=”Optimizer yeni indexi Index Seek operatörüyle kullanıyor.”]
Sayfa ve extent mimarisi
SQL Server veri dosyalarını mantıksal olarak 8 KiB büyüklüğünde sayfalara böler. Disk I/O veri dosyalarında sayfa seviyesinde gerçekleşir. Sekiz fiziksel olarak ardışık sayfa bir extent oluşturur; dolayısıyla bir extent 64 KiB’dir. Transaction log dosyaları ise bu sayfa yapısını kullanmaz, log record dizilerinden oluşur.
[company_article_image src=”articles/index-architecture/08-page-architecture.png” alt=”SQL Server veri sayfası mimarisi” caption=”SQL Server veri sayfasının temel bölümleri.”]
[company_article_image src=”articles/index-architecture/09-page-layout.png” alt=”SQL Server sayfa ve satır yerleşimi” caption=”Sayfa üzerindeki kayıtlar ve slot array ilişkisi.”]
Bu nedenle sorgu analizinde yalnızca dönen satır sayısına bakmak yetmez. Bir sorgunun kaç data ve index sayfasına dokunduğu, buffer pool üzerindeki etkisini anlamak için önemli bir sinyaldir.
B+ tree yapısı
Disk tabanlı rowstore indexler B+ tree yapısında düzenlenir:
[company_article_image src=”articles/index-architecture/10-btree-structure.png” alt=”SQL Server B+ tree root intermediate ve leaf seviyeleri” caption=”Rowstore indexte root, intermediate ve leaf seviyeleri.”]
- Root level: Aramanın başladığı üst düğümdür.
- Intermediate level: Büyük indexlerde doğru alt sayfaya yönlendiren ara seviyelerdir.
- Leaf level: Clustered indexta tablonun veri sayfalarını; nonclustered indexta ise index anahtarlarını, dahil edilen sütunları ve satır konumlandırıcısını içerir.
Tabloda clustered index yoksa tablo bir heap olarak saklanır. Heap üzerinde nonclustered index bulunabilir; dolayısıyla heap, “hiç indexi olmayan tablo” anlamına gelmez. Clustered index varsa tablonun satırları clustered anahtara göre B+ tree leaf seviyesinde organize edilir ve bir tabloda yalnızca bir clustered index olabilir.
Temel index türleri
Clustered index
Leaf seviyesinde veri sayfalarını içerir. Anahtar seçiminde dar, kararlı, mümkünse artan ve benzersizliğe yakın değerler genellikle daha iyi başlangıç noktalarıdır; ancak karar gerçek erişim desenine göre verilmelidir.
CREATE CLUSTERED INDEX CX_Employees_EmployeeID
ON dbo.Employees (EmployeeID);
Nonclustered index
Tablo verisinden ayrı bir erişim yapısıdır. Sık kullanılan filtre, join ve sıralama sütunlarına göre tasarlanabilir.
CREATE NONCLUSTERED INDEX IX_Employees_FirstName
ON dbo.Employees (FirstName);
Unique index
Anahtar değerlerinin benzersizliğini uygular ve veri bütünlüğünü korur. Birincil anahtar veya unique constraint oluşturulduğunda SQL Server bunu destekleyen unique indexi de oluşturur.
CREATE UNIQUE NONCLUSTERED INDEX UX_Employees_Email
ON dbo.Employees (EmailAddress);
Filtered index
Tablonun iyi tanımlanmış bir alt kümesini indexler. Özellikle aktif kayıtlar veya işlenmemiş kuyruk satırları gibi seçici filtrelerde daha küçük depolama ve bakım maliyeti sağlayabilir.
CREATE NONCLUSTERED INDEX IX_Orders_Open
ON dbo.Orders (OrderDate, CustomerID)
WHERE Status = N'Open';
Composite ve covering index
Birden fazla key sütunu içeren index composite indextir. Sütun sırası önemlidir ve sorguların gerçek predicate yapısıyla uyumlu olmalıdır. INCLUDE ile leaf seviyesine eklenen non-key sütunlar, sorgunun tüm verisini indexten karşılayarak bazı Key Lookup operasyonlarını kaldırabilir.
CREATE NONCLUSTERED INDEX IX_Employees_Name
ON dbo.Employees (FirstName, LastName)
INCLUDE (Title, City);
Covering index her zaman doğru çözüm değildir. Çok geniş include listeleri index boyutunu ve DML maliyetini artırır. Kazanç; okuma sıklığı, lookup maliyeti ve yazma yükü birlikte ölçülerek doğrulanmalıdır.
[company_article_image src=”articles/index-architecture/11-key-lookup-plan.png” alt=”SQL Server Key Lookup içeren yürütme planı” caption=”Sorgunun ihtiyaç duyduğu sütunlar indexten karşılanmadığında oluşan Key Lookup.”]
Index LastName ve Title sütunlarını INCLUDE ile kapsayacak biçimde güncellendiğinde aynı sorgu için lookup ihtiyacı ortadan kalkar:
CREATE NONCLUSTERED INDEX IX_Employees_FirstName
ON dbo.Employees (FirstName)
INCLUDE (LastName, Title)
WITH (ONLINE = ON, DROP_EXISTING = ON);
[company_article_image src=”articles/index-architecture/12-covering-index-plan.png” alt=”Covering index sonrasında Key Lookup içermeyen Index Seek planı” caption=”Covering index sonrasında doğrudan Index Seek ile tamamlanan plan.”]
Columnstore index
Columnstore, veriyi sütun bazında depolayarak analitik ve büyük tarama iş yüklerinde yüksek sıkıştırma ve batch-mode yürütme avantajları sunabilir. OLTP ve analitik iş yükünün karakterine göre clustered veya nonclustered columnstore değerlendirilebilir.
CREATE CLUSTERED COLUMNSTORE INDEX CCI_FactSales
ON dbo.FactSales;
CREATE NONCLUSTERED COLUMNSTORE INDEX NCCI_Sales_Analytics
ON dbo.Sales (OrderDate, CustomerID, TotalAmount);
Index yaşam döngüsü işlemleri
ALTER INDEX IX_Employees_FirstName
ON dbo.Employees DISABLE;
DROP INDEX IX_Employees_FirstName
ON dbo.Employees;
Disable edilen clustered index tablonun verisine erişimi de engeller; bu nedenle üretimde etkisi anlaşılmadan uygulanmamalıdır.
Önemli CREATE INDEX seçenekleri
FILLFACTOR ve PAD_INDEX
FILLFACTOR, create veya rebuild anında leaf sayfalarının ne kadar dolu bırakılacağını belirler; boş alan sürekli korunmaz. Daha düşük değer olası page split baskısını azaltabilir fakat indexi büyütür. PAD_INDEX aynı doluluk yaklaşımının ara seviyelere uygulanmasını kontrol eder.
SORT_IN_TEMPDB
Index oluşturma sırasındaki geçici sıralama sonuçlarını tempdb üzerinde tutar. Kaynaklar doğru planlandıysa kullanıcı veritabanındaki geçici alan baskısını azaltabilir; tempdb kapasitesi ve I/O yolu ayrıca değerlendirilmelidir.
DROP_EXISTING, ONLINE ve MAXDOP
DROP_EXISTING, var olan indexi yeni tanımla yeniden oluşturmak için kullanılabilir. ONLINE, desteklenen operasyonlarda erişilebilirliği artırır ancak kısa süreli kilitler ve kaynak tüketimi tamamen ortadan kalkmaz. MAXDOP, index operasyonunun paralellik derecesini sınırlar; sunucudaki diğer iş yükleri dikkate alınarak seçilmelidir.
IGNORE_DUP_KEY
Unique index üzerinde bu seçenek açık olduğunda çok satırlı insert sırasında duplicate key oluşturan satırlar atlanabilir ve uyarı üretilebilir. Bu davranış veri kalitesini gizleyebileceği için uygulama beklentisi net değilse varsayılan OFF daha güvenli bir tercihtir.
DATA_COMPRESSION
ROW ve PAGE sıkıştırma disk ve buffer pool kullanımını azaltabilir; karşılığında CPU maliyeti oluşturabilir. Kazanç tablo ve index özelinde ölçülmelidir.
ALTER INDEX IX_Employees_Name
ON dbo.Employees
REBUILD WITH (DATA_COMPRESSION = PAGE);
Karar kontrol listesi
- Sorguyu gerçek parametreler ve temsilî veri hacmiyle ölçün.
- Actual execution plan, logical reads, CPU ve elapsed time verilerini birlikte inceleyin.
- Mevcut indexlerin yeni ihtiyacı karşılayıp karşılamadığını kontrol edin.
- Yeni indexin INSERT, UPDATE, DELETE ve bakım maliyetini hesaba katın.
- Değişikliği kontrollü ortamda test edin; üretim sonrası yeniden ölçün.
İyi index tasarımı, her sorguya yeni bir index eklemek değildir. Amaç, iş yükünün tamamı için okuma kazancı ile yazma ve bakım maliyeti arasında ölçülebilir bir denge kurmaktır.