Tek Index, Birden Fazla Sütun
Composite index — yani çok sütunlu index — iki ya da daha fazla sütun üzerine kurulan tek bir index'tir. Oluştururken sütunları sırayla yazmanız yeterli:
idx_orders_customer_status indexi, kayıtları önce customer_id'ye, ardından her müşteri içinde status'a göre sıralı tutar. Bu sıralama meselenin tamamı — composite index'lerle ilgili geri kalan her şey buradan türüyor.
Zihinsel Model: Sıralı Bir Telefon Rehberi
Eski tarz bir telefon rehberi düşün. Kayıtlar önce soyada göre, aynı soyad içinde ise ada göre sıralı. (last_name, first_name) üzerindeki bir index aynen böyle görünür.
Bazı sorgular ucuza gelir, bazıları gelmez:
- "Soyadı Patel olan herkesi bul" — kolay, tüm Patel'ler yan yana.
- "Priya Patel'i bul" — kolay, Patel'e atla, sonra Priya'ya kadar tara.
- "Adı Priya olan herkesi bul" — yavaş, her sayfayı baştan sona taramanız gerekir. Priya'lar her soyadın altına dağılmış durumda.
SQLite'taki çok sütunlu bir index de tam olarak böyle çalışır. İlk sütun birincil sıralama anahtarıdır; ikinci sütun yalnızca ilk sütun değeri aynı olan kayıtları kendi içinde sıralar.
Leftmost Prefix Kuralı
SQLite, bir composite index'i ancak WHERE koşulu o index'in sütunlarının en soldaki bir önekini (leftmost prefix) kısıtlıyorsa kullanabilir. (a, b, c) üzerindeki bir index için:
- Sadece
aile filtreleme — index kullanılır. avebile filtreleme — index kullanılır.a,bvecile filtreleme — index kullanılır.- Tek başına
b, tek başınacya dabvecile filtreleme — index kullanılmaz.
Bunu doğrudan EXPLAIN QUERY PLAN ile doğrulayabilirsiniz:
İlk planda SEARCH events USING INDEX idx_events_user_kind_time görüyoruz. İkincisi ise SCAN events'e düşüyor — sadece kind üzerinden filtrelemek baştaki user_id sütununu atladığı için index bu sorguda işe yaramıyor.
Sütun Sırası Bir Tasarım Kararıdır
Leftmost prefix kuralı devreye girdiğinden, CREATE INDEX içinde sütunları hangi sırayla yazdığınız stilistik bir tercih değil, gerçek bir karardır. İki pratik kural:
- En sık filtrelediğiniz sütunu başa koyun. O sütun, index'i en geniş sorgu yelpazesine açar.
- Eşitlik (equality) sütunlarını aralık (range) sütunlarından önce yazın. SQLite önce
=ile index'in derinine inip ardından<,>veyaBETWEENile bitişik bir aralığı tarayabilir — ama bunu yalnızca kullanılan son sütunda yapabilir.
Plan çıktısında şunu görüyorsunuz: SEARCH sales USING INDEX idx_sales_region_time (region=? AND sold_at>?). SQLite doğrudan region = 'EU' satırlarına atlıyor, ardından tarih aralığı boyunca ileri doğru ilerliyor. Sütun sırasını (sold_at, region) şeklinde değiştirdiğinde ise aynı sorgu, tarih aralığındaki tüm satırları taramak ve her biri için region değerini tekrar kontrol etmek zorunda kalıyor.
Composite Index mi, Birden Fazla Tek Sütunlu Index mi?
Sıkça karşılaşılan bir soru: (a, b) üzerinde tek bir index mi oluşturmalıyım, yoksa a ve b için ayrı ayrı iki index mi?
Birleşik filtrede composite index açık ara daha hızlıdır — SQLite eşleşen (project_id, state) kayıtlarına doğrudan gider. İki ayrı tek sütunlu index olduğunda ise SQLite genelde birini seçip satırları daraltır, sonra eşleşen her satırda diğer sütunu yeniden kontrol eder. Bazen iki index'i kesişim yoluyla kullanabilir, ancak iki sütun birlikte sorgulandığında çok sütunlu index (composite index) çok daha temiz bir çözümdür.
project_id ve state ayrı ayrı da sorgulanıyorsa ikisini birden tutmak isteyebilirsiniz: birleşik filtre için composite index, sadece state üzerinden filtre uygulayan sorgular için de tek sütunlu bir index.
Covering Index (Kapsayıcı Index)
Bir index, sorgunun ihtiyaç duyduğu tüm sütunları — hem filtre sütunlarını hem de seçilen sütunları — içeriyorsa SQLite yanıtı tabloya hiç dokunmadan üretebilir. Buna covering index denir ve bir sorgunun ulaşabileceği en yüksek hız budur.
Planda USING COVERING INDEX idx_invoices_cover ifadesini görüyorsunuz. Sorgu issued_at ve total değerlerini doğrudan index'ten okuyor; notes ve id gerekmediği için tabloya hiç dokunulmuyor. Sürekli çalışan bir sorguyu komple karşılayabilmek adına composite index'e fazladan bir sütun eklemek, bu tarz durumlarda kesinlikle değer.
Composite UNIQUE Kısıtlamaları
SQLite composite index'leri, sütun kombinasyonları üzerinde benzersizliği zorlamak için de kullanılır. Tek başına hiçbir sütunun unique olmadığı, ama kombinasyonun unique olması gereken senaryolarda işe yarar:
Üçüncü insert şu hatayı fırlatır: UNIQUE constraint failed: enrollments.student_id, enrollments.course_id. Aynı çift zaten index'te mevcut olduğu için SQLite mükerrer kaydı reddeder.
Dikkat Edilmesi Gereken Tuzaklar
- Baştaki olmayan sütunlar arasındaki
ORindex'i devre dışı bırakır.(a, b)index'i üzerindeWHERE a = 1 OR b = 2sorgusu genellikle index'ten hiç faydalanamaz — SQLite iki dalı ayrı ayrı değerlendirmek zorunda kalır. - Index'lenmiş sütunlar üzerindeki fonksiyonlar index'i etkisiz kılar.
WHERE lower(email) = 'x'ifadesiemailsütunundaki index'i kullanmaz. Bunun yerine ifadenin kendisini index'leyin ya da veriyi insert sırasında normalize edin. - Index'ler bedava değildir. Her
INSERT, (index'li sütunları etkileyen)UPDATEveDELETEişleminde tüm index'ler güncellenir. Yazma yükü ağır bir tabloda üç tane composite index, yazma maliyetinin büyük kısmını yutabilir. - Index'leri oluşturduktan sonra mutlaka
ANALYZEçalıştırın. SQLite'ın sorgu planlayıcısı, aday index'ler arasında seçim yaparkenANALYZEile toplanan istatistiklere bakar. Bu istatistikler yoksa, her zaman optimal olmayan sezgisel kurallara geri döner.
Pratik Bir İş Akışı
Yavaş bir sorguyu iyileştirirken döngü genellikle şöyle ilerler:
-
Sorgu üzerinde
EXPLAIN QUERY PLANçalıştırın ve SQLite'ın şu an ne yaptığını görün. -
Tablo taraması yapıyorsa
WHEREcümlesine bakın — eşitlik sütunu hangisi? Aralık (range) sütunu hangisi? Hangi sütunlar seçiliyor? -
Önce eşitlik, sonra aralık olacak şekilde sıralanmış bir composite index kurun; covering index işe yarayacaksa seçilen sütunları da sona ekleyin.
-
ANALYZEçalıştırın. -
EXPLAIN QUERY PLAN'ı tekrar çalıştırın. Planın değiştiğini ve index'in kullanıldığını doğrulayın. -
Sorguyu, gerçeği temsil eden veriler üzerinde önce ve sonra olarak ölçün.
-
adımı atlarsanız sonuçlarına katlanırsınız. Planda doğru görünen bir index, tablo küçükse ya da planlayıcı farklı bir yol seçerse pratikte daha yavaş kalabilir.
Sırada: Partial Index'ler
Composite index'ler tablodaki her satırı kapsar. Ama çoğu zaman yalnızca küçük bir satır kümesi önemlidir — açık biletler, işlenmemiş işler, silinmemiş kayıtlar. Partial index, index'in kendisine gömülü bir WHERE cümlesiyle yalnızca o satırları index'lemenizi sağlar. Bir sonraki sayfanın konusu bu.
Sıkça Sorulan Sorular
SQLite'ta composite index nedir?
Composite index, iki ya da daha fazla sütunu birden kapsayan tek bir indekstir. CREATE INDEX idx_name ON table(col_a, col_b) şeklinde oluşturursunuz. SQLite kayıtları önce col_a'ya göre, sonra her col_a değeri içinde col_b'ye göre sıralı tutar — tıpkı önce soyada, ardından ada göre sıralanmış bir telefon rehberi gibi.
SQLite composite index'te sütun sırası önemli mi?
Hem de çok. SQLite bir composite index'i ancak WHERE koşulu indekslenmiş sütunların soldan başlayan bir önekini (leftmost prefix) filtreliyorsa kullanabilir. (a, b, c) üzerindeki bir indeks; sadece a, a ve b, ya da üçünün birden filtrelendiği sorgulara yardım eder — ama yalnızca b veya yalnızca c üzerinden filtreleyen bir sorguya hiçbir faydası olmaz.
Tek sütunlu indeksler yerine ne zaman composite index kullanmalıyım?
Sorgularınız sürekli aynı sütun kombinasyonu üzerinden filtreleme veya sıralama yapıyorsa composite index mantıklıdır. Sütunlar bağımsız olarak sorgulanıyorsa ayrı ayrı tek sütunlu indeksler daha uygundur. SQLite'ın hangi indeksi gerçekten seçtiğini görmek için EXPLAIN QUERY PLAN çalıştırın — güvenebileceğiniz tek geri bildirim budur.
SQLite'ta covering index nedir?
Covering index, sorgunun ihtiyaç duyduğu tüm sütunları içeren bir indekstir; böylece SQLite cevabı doğrudan indeksten döndürür, tabloya hiç dokunmaz. Bu durumda EXPLAIN QUERY PLAN çıktısında USING COVERING INDEX ifadesini görürsünüz. Sık çalışan bir sorguyu hızlandırmak için composite index'e fazladan sütun eklemek yaygın bir optimizasyon yöntemidir.