SQL Index Nedir? Veritabanı Sorgularını Hızlandırmanın Yolu
İçindekiler
- SQL Index Neden Gereklidir?
- Index Nasıl Çalışır? B-Tree Yapısı Nedir?
- SQL Index Türleri Nelerdir?
- Index Nasıl Oluşturulur?
- Bileşik Index Nedir ve Sütun Sırası Neden Önemlidir?
- EXPLAIN ile Sorgunun Index Kullandığı Nasıl Anlaşılır?
- Hangi Sütunlara Index Eklenmelidir?
- Index Ne Zaman Zarar Verir?
- Index Varken Sorgu Neden Hâlâ Yavaş Olabilir?
- Sütun Üzerinde Fonksiyon Kullanmak
- Başında Joker Karakter Olan LIKE Araması
- Veri Tipi Uyuşmazlığı
- Sahadan Bir Örnek: Stok Hareket Raporu
- Özet: Index Kullanırken Kontrol Listesi
SQL index (dizin), bir tablodaki bir veya birkaç sütunun değerlerini sıralı biçimde tutan ve veritabanının aranan satıra tüm tabloyu okumadan ulaşmasını sağlayan ek bir veri yapısıdır. Kitabın sonundaki kavram dizini gibi çalışır. Aradığınız kelimenin hangi sayfada olduğunu dizinden bulur, doğrudan o sayfaya gidersiniz. Index yoksa veritabanı her sorguda tabloyu satır satır tarar. Buna tam tablo taraması (full table scan) denir.
Bu yazıda index yapısının nasıl çalıştığını, MySQL ve SQLite üzerinde nasıl oluşturulduğunu, hangi sütunlara eklenmesi gerektiğini ve EXPLAIN komutuyla sorgunun index kullanıp kullanmadığını nasıl kontrol edeceğinizi örneklerle anlatacağız.
SQL Index Neden Gereklidir?
Küçük tablolarda index olmaması fark edilmez. Birkaç yüz satırlık bir tabloyu taramak milisaniyeler sürer. Sorun, tablo büyüdükçe ortaya çıkar. Sipariş, log, stok hareketi veya ziyaret kaydı gibi sürekli büyüyen tablolarda her sorgu tüm satırları okumak zorunda kalırsa sayfa açılış süreleri uzar ve sunucu yükü artar.
Index ile veritabanı motoru aranan değeri sıralı yapı üzerinde ikili aramaya benzer bir yöntemle bulur. Okunması gereken satır sayısı tablonun tamamı yerine yalnızca eşleşen kayıtlarla sınırlı kalır.
- WHERE koşullarında filtrelemeyi hızlandırır.
- JOIN işlemlerinde tabloların eşleştirilmesini hızlandırır.
- ORDER BY sıralamasında ayrı bir sıralama adımına gerek bırakmayabilir.
- UNIQUE index ile yinelenen kayıt girilmesini veritabanı seviyesinde engeller.
Index Nasıl Çalışır? B-Tree Yapısı Nedir?
MySQL (InnoDB) ve SQLite dahil ilişkisel veritabanlarının çoğu varsayılan olarak B-Tree (dengeli ağaç) yapısını kullanır. B-Tree, değerleri sıralı tutan ve her düğümde birden fazla anahtar barındıran çok dallı bir ağaçtır. Ağaç dengeli kaldığı için kök düğümden yaprak düğüme giden yol kısadır ve milyonlarca satırda bile birkaç adımda hedefe ulaşılır.
Yaprak düğümlerde index değeri ile birlikte satırın tablodaki konumunu gösteren bir işaretçi bulunur. InnoDB tarafında bu işaretçi birincil anahtardır. SQLite tarafında ise rowid değeridir. Veritabanı önce index üzerinde arama yapar, ardından işaretçiyi kullanarak asıl satıra gider.
Değerler sıralı tutulduğu için B-Tree yalnızca eşitlik aramasında değil, aralık sorgularında da etkilidir. Tarih aralığı, fiyat aralığı veya belirli bir harfle başlayan metin aramaları bu yüzden index ile hızlanır.
SQL Index Türleri Nelerdir?
| Index Türü | Ne İşe Yarar? | Örnek Kullanım |
|---|---|---|
| Primary Key | Her satırı benzersiz tanımlar, otomatik olarak index oluşturur | id sütunu |
| Unique Index | Aynı değerin iki kez girilmesini engeller ve aramayı hızlandırır | e-posta, TC kimlik, barkod |
| Normal (Tekli) Index | Tek sütun üzerinde arama ve sıralamayı hızlandırır | sipariş tarihi, kategori |
| Bileşik (Composite) Index | Birden fazla sütunu tek index içinde birlikte tutar | müşteri ve tarih birlikte |
| Full-Text Index | Uzun metinlerde kelime bazlı arama yapar | makale içeriği araması |
Foreign key tanımlandığında MySQL InnoDB, ilgili sütunda index yoksa otomatik olarak oluşturur. SQLite ise foreign key sütunları için otomatik index oluşturmaz. Bu fark, SQLite kullanılan projelerde JOIN sorgularının beklenmedik şekilde yavaş çalışmasının yaygın sebeplerinden biridir.
Index Nasıl Oluşturulur?
Index oluşturma komutu MySQL ve SQLite tarafında neredeyse aynıdır. Örnek olarak bir sipariş tablosu üzerinden ilerleyelim.
CREATE TABLE siparisler (
id INTEGER PRIMARY KEY,
musteri_id INTEGER NOT NULL,
durum VARCHAR(20) NOT NULL,
tutar DECIMAL(10,2) NOT NULL,
siparis_tarihi DATETIME NOT NULL
);
-- Tekli index
CREATE INDEX idx_siparis_tarih ON siparisler (siparis_tarihi);
-- Bileşik index
CREATE INDEX idx_siparis_musteri_tarih ON siparisler (musteri_id, siparis_tarihi);
-- Benzersiz index
CREATE UNIQUE INDEX ux_kullanici_eposta ON kullanicilar (eposta);
Index silmek için kullanılan sözdizimi iki veritabanında farklıdır:
-- MySQL
DROP INDEX idx_siparis_tarih ON siparisler;
-- SQLite
DROP INDEX idx_siparis_tarih;
Index adlandırmada tutarlı bir kural kullanmak bakımı kolaylaştırır. idx_tablo_sutun normal index için, ux_tablo_sutun benzersiz index için pratik bir düzendir. Aylar sonra şemaya baktığınızda hangi indexin ne işe yaradığını adından anlarsınız.
Bileşik Index Nedir ve Sütun Sırası Neden Önemlidir?
Bileşik index, birden fazla sütunu soldan sağa sıralı şekilde tek bir yapıda tutar. Telefon rehberindeki soyad ve ad sıralamasına benzer. Rehber önce soyada, soyadı aynı olanlar kendi içinde ada göre sıralanır.
Bu yapının önemli bir sonucu vardır: en soldaki sütun kuralı. (musteri_id, siparis_tarihi) indexi şu sorgularda kullanılır:
WHERE musteri_id = 42WHERE musteri_id = 42 AND siparis_tarihi >= '2026-01-01'WHERE musteri_id = 42 ORDER BY siparis_tarihi DESC
Ancak yalnızca WHERE siparis_tarihi >= '2026-01-01' koşulu bu indexi verimli kullanamaz, çünkü indexin ilk sütunu olan müşteri numarası sorguda yoktur. Rehberde soyadını bilmeden yalnızca ada göre arama yapmaya benzer.
Pratik kural şudur: eşitlik (=) ile filtrelenen sütunlar öne, aralık (>, <, BETWEEN) ile filtrelenen veya sıralamada kullanılan sütunlar arkaya yazılır.
EXPLAIN ile Sorgunun Index Kullandığı Nasıl Anlaşılır?
Bir sorgunun index kullanıp kullanmadığını tahmin etmek yerine ölçmek gerekir. Bunun için sorgunun başına EXPLAIN eklenir.
-- MySQL
EXPLAIN SELECT * FROM siparisler
WHERE musteri_id = 42 AND siparis_tarihi >= '2026-01-01';
-- SQLite
EXPLAIN QUERY PLAN SELECT * FROM siparisler
WHERE musteri_id = 42 AND siparis_tarihi >= '2026-01-01';
MySQL çıktısında dikkat edilmesi gereken sütunlar şunlardır:
| Sütun | Anlamı | İyi mi Kötü mü? |
|---|---|---|
| type = ALL | Tüm tablo taranıyor | Büyük tabloda kötü |
| type = ref veya range | Index ile eşitlik veya aralık araması yapılıyor | İyi |
| type = const | Primary key veya unique ile tek satır bulunuyor | En iyi |
| key | Kullanılan indexin adı | Boş ise index kullanılmıyor |
| rows | Okunacağı tahmin edilen satır sayısı | Ne kadar düşükse o kadar iyi |
SQLite tarafında çıktı daha sadedir. SCAN siparisler ifadesi tam tablo taraması anlamına gelir. SEARCH siparisler USING INDEX idx_siparis_musteri_tarih ifadesi ise indexin kullanıldığını gösterir.
Hangi Sütunlara Index Eklenmelidir?
Her sütuna index eklemek çözüm değildir. Index eklenecek sütunları belirlemek için uygulamanın gerçekten çalıştırdığı sorgulara bakmak gerekir.
- WHERE koşulunda sık kullanılan sütunlar: durum, kategori, kullanıcı numarası gibi.
- JOIN ile bağlanan sütunlar: özellikle foreign key sütunları.
- ORDER BY ile sıralanan sütunlar: listelerde tarihe göre sıralama gibi.
- Benzersiz olması gereken sütunlar: e-posta, kullanıcı adı, barkod.
Seçiciliği düşük sütunlarda index genellikle işe yaramaz. Yalnızca iki farklı değer alan aktif veya pasif gibi bir sütunda veritabanı motoru çoğu zaman indexi atlayıp tabloyu taramayı tercih eder. Bu tür sütunlar tek başına değil, bileşik indexin parçası olarak daha anlamlıdır.
Index Ne Zaman Zarar Verir?
Index okuma işlemlerini hızlandırırken yazma işlemlerine ek yük getirir. Her INSERT, UPDATE ve DELETE işleminde veritabanı ilgili tüm indexleri de güncellemek zorundadır.
- Çok sayıda index, toplu veri aktarımlarını yavaşlatır.
- Her index diskte ek alan kaplar.
- Birbirini kapsayan indexler gereksiz yük oluşturur.
(musteri_id, siparis_tarihi)varken ayrıca yalnızca(musteri_id)indexi tutmaya genellikle gerek yoktur.
Toplu veri aktarımı yapılacaksa pratik bir yöntem, işlemi tek transaction içinde yapmak ve gerekiyorsa aktarım sonrasında indexleri oluşturmaktır.
Index Varken Sorgu Neden Hâlâ Yavaş Olabilir?
Index tanımlı olduğu halde sorgunun onu kullanmamasının birkaç yaygın sebebi vardır.
Sütun Üzerinde Fonksiyon Kullanmak
Aşağıdaki sorgu, sipariş tarihi üzerinde index olsa bile tüm tabloyu tarar. Çünkü veritabanı her satırda fonksiyonu çalıştırıp sonucu karşılaştırmak zorundadır.
-- Index kullanılamaz
SELECT * FROM siparisler WHERE YEAR(siparis_tarihi) = 2026;
-- Index kullanılır
SELECT * FROM siparisler
WHERE siparis_tarihi >= '2026-01-01' AND siparis_tarihi < '2027-01-01';
Başında Joker Karakter Olan LIKE Araması
LIKE 'kalem%' araması index kullanabilir, çünkü sıralı yapıda başlangıç noktası bellidir. LIKE '%kalem%' ise index kullanamaz. İçerik içinde arama gerekiyorsa MySQL tarafında FULLTEXT, SQLite tarafında FTS5 sanal tabloları daha doğru çözümdür.
Veri Tipi Uyuşmazlığı
Metin tipinde tutulan bir telefon veya barkod sütununu sayı ile karşılaştırmak, bazı durumlarda örtük tip dönüşümüne ve indexin devre dışı kalmasına yol açar. Karşılaştırmayı her zaman sütunun kendi tipinde yapmak gerekir.
Sahadan Bir Örnek: Stok Hareket Raporu
Fabrikada kullandığımız stok takip uygulamasında hareket tablosu ilk aylarda sorunsuz çalışıyordu. Kayıt sayısı büyüdükçe belirli bir ürün kodu için tarih aralığı raporu belirgin şekilde yavaşlamaya başladı. EXPLAIN çıktısında sorgunun tüm tabloyu taradığı açıkça görünüyordu.
Sorgu hem ürün koduna eşitlikle hem de tarihe aralıkla filtreleme yapıyordu. Bu yüzden (urun_kodu, hareket_tarihi) sırasıyla bileşik index eklendi. Index sonrasında EXPLAIN çıktısı tam tarama yerine index araması gösterdi ve rapor ekranı kullanıcıyı bekletmeden açılır hale geldi. Aynı projede önceden eklenmiş ama hiçbir sorguda kullanılmayan iki index de kaldırıldı ve günlük toplu veri aktarımı hızlandı.
Bu deneyimden çıkan ders şudur: index eklemeden önce yavaş sorguyu tespit etmek, EXPLAIN ile doğrulamak ve index sonrasında tekrar ölçmek gerekir. Tahmine dayalı index eklemek çoğu zaman gereksiz yük getirir.
Özet: Index Kullanırken Kontrol Listesi
- Yavaş çalışan sorguyu belirleyin.
- EXPLAIN ile tam tablo taraması olup olmadığına bakın.
- WHERE, JOIN ve ORDER BY içinde kullanılan sütunları listeleyin.
- Eşitlik sütunları önde olacak şekilde bileşik index tasarlayın.
- Sütun üzerinde fonksiyon kullanan koşulları aralık sorgusuna çevirin.
- Index sonrası EXPLAIN çıktısını tekrar kontrol edin.
- Kullanılmayan ve birbirini kapsayan indexleri temizleyin.

0 Yorum