---
title: 'Dizin Türleri'
source: 'https://academia.sh/tr/kurslar/veritabani-yonetimi/dizin-turleri'
course: 'İlişkisel Veritabanı Yönetimi'
language: tr
updated: '2026-08-17T18:08:56+00:00'
license: 'CC BY-SA 4.0'
---

# Dizin Türleri

Dizinsiz yabancı anahtarın ölçülen bedeli, ağaç tabanlı dizinin aralık ve sıra yeteneği, karma tabanlı yaklaşımın yalnız eşitlikte çalışması ve sözcük düzeyinde arama için ters dizin.

Motor Mimarisi konusu sistem kataloğuyla kapandı ve arkasında tek bir bulgu bıraktı:
kitap tablosundaki şube başvurusu dizinsizdi. O bulgu bir tavsiye biçiminde yazılmıştı —
"yabancı anahtar taşıyan her sütunun kendi dizini olmalıdır". Tavsiyeler ölçülmediği
sürece kanı olarak kalır.

Bu ders o bulguyu önce bir sayıya çevirir, sonra asıl sorusuna geçer. Dizin, Sorgu
Başarımı konusunda tek bir yapı gibi anlatılmıştı: sıralı tutulan bir kopya. Gerçekte
dizin bir yapı değil bir yapı ailesidir ve aileler birbirinden, hangi soruya yanıt
verebildikleriyle ayrılır. Bir dizin türü eşitliği yanıtlar ama aralığı yanıtlamaz;
başka biri değerin tamamını değil içindeki sözcükleri bulur. Seçim, sorunun biçimine
bakılarak yapılır.

## Askıda Kalan Bulgu

Yabancı anahtar kısıtı, bir tablodaki değerin başka bir tabloda var olmasını zorunlu
kılar. Bu zorunluluğun bir de ters yönü vardır: ana tablodan bir satır silinirken motor,
o satıra başvuran bir çocuk satır kalmadığını doğrulamak zorundadır. Doğrulamanın nasıl
yapılacağını belirleyen şey, çocuk sütunda dizin olup olmamasıdır.

```sh
rm -f fk.db fk_dizinli.db
sqlite3 fk.db <<'SQL'
PRAGMA foreign_keys = ON;
CREATE TABLE sube (sube_id INTEGER PRIMARY KEY, ad TEXT NOT NULL);
CREATE TABLE kitap (kitap_id INTEGER PRIMARY KEY, baslik TEXT NOT NULL,
                    sube_id INTEGER NOT NULL REFERENCES sube(sube_id));
INSERT INTO sube VALUES (1,'Merkez'),(2,'Bahcelievler'),(3,'Kadikoy'),(4,'Beyoglu'),
                        (5,'Konak'),(6,'Nilufer'),(7,'Selcuklu'),(8,'Cankaya'),(9,'Depo');
INSERT INTO kitap (kitap_id, baslik, sube_id)
WITH RECURSIVE s(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM s WHERE n < 200000)
SELECT n, 'Kitap ' || n, 1 + (n % 8) FROM s;
SQL
cp fk.db fk_dizinli.db
sqlite3 fk_dizinli.db 'CREATE INDEX kitap_sube ON kitap(sube_id);'

for db in fk.db fk_dizinli.db; do
  printf '%s: ' "$db"
  sqlite3 "$db" <<'SQL'
PRAGMA foreign_keys = ON;
.stats vmstep
DELETE FROM sube WHERE sube_id = 9;
SQL
done
```

```
fk.db: VM-steps: 600013
fk_dizinli.db: VM-steps: 15
```

Silinen şube hiç kitap taşımıyordu; iki koşumda da silinen satır sayısı birdir. Fark
yapılan işte: dizinsiz kopyada motor, iki yüz bin kitabın hepsine tek tek bakıp hiçbirinin
dokuzuncu şubeye ait olmadığını doğruladı. Dizinli kopyada aynı doğrulama on beş adım
sürdü — dizine girip o anahtarın hiç girdisi olmadığını görmek yeterliydi.

Buradaki oran kırk bin kattır ve tabloyla birlikte büyür. Sonuç, kataloğa yazılan
denetimin gerekçesidir: yabancı anahtar sütununda dizin, okuma başarımı için değil,
**ana tablodaki her silme ve anahtar güncellemesi** için gereklidir. Bu maliyet sorgu
planlarında görünmez, çünkü onu doğuran deyim `DELETE` deyimidir ve pahalı olan kısım
kısıt denetimidir.

## Ağaç Tabanlı Dizin

Sorgu Başarımı konusunda kullanılan dizin türü **ağaç tabanlı dizindir (tree index)**:
girdileri anahtar değerine göre sıralı tutan, çoğunlukla B-ağacı biçiminde bir yapı.
Ölçümler kütüphane veri kümesi üzerinde yapılır; aşağıdaki kurulum bütün konu boyunca
kullanılacaktır.

```sh
rm -f kutuphane.db
cat > kurulum.sql <<'SQL'
CREATE TABLE uye (
  uye_id       INTEGER PRIMARY KEY,
  ad           TEXT NOT NULL,
  sehir        TEXT NOT NULL,
  kayit_tarihi TEXT NOT NULL
);
CREATE TABLE odunc (
  odunc_id    INTEGER PRIMARY KEY,
  kitap_id    INTEGER NOT NULL,
  uye_id      INTEGER NOT NULL,
  alis_tarihi TEXT NOT NULL,
  iade_tarihi TEXT
);
INSERT INTO uye (uye_id, ad, sehir, kayit_tarihi)
WITH RECURSIVE sayac(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM sayac WHERE n < 120000)
SELECT n, 'Uye ' || n,
       CASE n % 5 WHEN 0 THEN 'Ankara' WHEN 1 THEN 'Istanbul' WHEN 2 THEN 'Izmir'
                  WHEN 3 THEN 'Bursa' ELSE 'Konya' END,
       date('2015-01-01', '+' || (n % 3200) || ' days')
FROM sayac;
INSERT INTO odunc (odunc_id, kitap_id, uye_id, alis_tarihi, iade_tarihi)
WITH RECURSIVE sayac(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM sayac WHERE n < 2000000)
SELECT n, 1 + ((n * 7) % 200000), 1 + ((n * 13) % 120000),
       date('2018-01-01', '+' || ((n * 37) % 2437) || ' days'),
       CASE WHEN n % 9 = 0 THEN NULL
            ELSE date('2018-01-01', '+' || (((n * 37) % 2437) + 14) || ' days') END
FROM sayac;
SQL
sqlite3 kutuphane.db < kurulum.sql
sqlite3 kutuphane.db <<'SQL'
CREATE INDEX odunc_alis ON odunc(alis_tarihi);
.print '--- esitlik ---'
EXPLAIN QUERY PLAN SELECT count(*) FROM odunc WHERE alis_tarihi = '2021-03-15';
.stats vmstep
SELECT count(*) FROM odunc WHERE alis_tarihi = '2021-03-15';
.stats off
.print '--- aralik ---'
EXPLAIN QUERY PLAN
SELECT count(*) FROM odunc WHERE alis_tarihi BETWEEN '2021-03-15' AND '2021-03-21';
.stats vmstep
SELECT count(*) FROM odunc WHERE alis_tarihi BETWEEN '2021-03-15' AND '2021-03-21';
.stats off
.print '--- en kucuk deger ---'
EXPLAIN QUERY PLAN SELECT min(alis_tarihi) FROM odunc;
.stats vmstep
SELECT min(alis_tarihi) FROM odunc;
SQL
```

```
--- esitlik ---
QUERY PLAN
`--SEARCH odunc USING COVERING INDEX odunc_alis (alis_tarihi=?)
820
VM-steps: 2471
--- aralik ---
QUERY PLAN
`--SEARCH odunc USING COVERING INDEX odunc_alis (alis_tarihi>? AND alis_tarihi<?)
5744
VM-steps: 17245
--- en kucuk deger ---
QUERY PLAN
`--SEARCH odunc USING COVERING INDEX odunc_alis
2018-01-01
VM-steps: 14
```

Üç sorunun üçü de dizinden yanıtlandı ve nedeni tektir: **sıra**. Eşitlikte ağaçtan tek
bir anahtara inilir. Aralıkta alt uca inilir ve üst uç geçilene kadar yan yana ilerlenir;
2.471 adımdan 17.245 adıma çıkış, yedi katlık satır sayısıyla orantılıdır. En küçük değer
sorusu ise on dört adımda yanıtlandı — dizinin en solundaki girdi okundu ve iş bitti.

Sıranın getirdiği yetenekler bir listedir: eşitlik, aralık, ön ek, en küçük ve en büyük,
`ORDER BY` için hazır düzen, gruplamada tekrarların yan yana gelmesi. Ağaç tabanlı dizinin
her motorda varsayılan tür olmasının nedeni budur; tek bir yapı bu soruların hepsini
karşılar.

## Karma Tabanlı Dizin

**Karma tabanlı dizin (hash index)**, anahtarın kendisini değil, anahtarın bir karma
fonksiyonundan geçirilmiş değerini dizinler. Veri Yapıları kursundaki Karma Tabloları
dersinde kurulan yapı budur; buradaki fark, tablonun bellekte değil diskte durmasıdır.

Karma dizin her motorda bulunmaz. Aşağıdaki koşum onu bir **ifade dizini** ile modeller:
tarihin gün numarası çarpımsal bir karma fonksiyonundan geçirilir ve dizinlenen değer bu
karma olur. Model, gerçek bir karma dizinin belirleyici özelliğini birebir taşır —
dizindeki sıra, değerin sırası değildir.

```sh
rm -f karma.db
sqlite3 karma.db < kurulum.sql
sqlite3 karma.db <<'SQL'
-- Degerin kendisi degil, degerin carpimsal karmasi dizinlenir.
CREATE INDEX odunc_alis_karma ON odunc(
  (CAST(julianday(alis_tarihi) AS INTEGER) * 2654435761) % 4294967296 );
.print '--- aranan tarihin karma degeri ---'
SELECT (CAST(julianday('2021-03-15') AS INTEGER) * 2654435761) % 4294967296 AS karma;
.print '--- esitlik, karma uzerinden sorulunca ---'
EXPLAIN QUERY PLAN SELECT count(*) FROM odunc
  WHERE (CAST(julianday(alis_tarihi) AS INTEGER) * 2654435761) % 4294967296 = 2436359960;
.stats vmstep
SELECT count(*) FROM odunc
  WHERE (CAST(julianday(alis_tarihi) AS INTEGER) * 2654435761) % 4294967296 = 2436359960;
.stats off
.print '--- ayni esitlik, tarihin kendisi uzerinden sorulunca ---'
EXPLAIN QUERY PLAN SELECT count(*) FROM odunc WHERE alis_tarihi = '2021-03-15';
.stats vmstep
SELECT count(*) FROM odunc WHERE alis_tarihi = '2021-03-15';
.stats off
.print '--- aralik ---'
EXPLAIN QUERY PLAN
SELECT count(*) FROM odunc WHERE alis_tarihi BETWEEN '2021-03-15' AND '2021-03-21';
.stats vmstep
SELECT count(*) FROM odunc WHERE alis_tarihi BETWEEN '2021-03-15' AND '2021-03-21';
SQL
```

```
--- aranan tarihin karma degeri ---
2436359960
--- esitlik, karma uzerinden sorulunca ---
QUERY PLAN
`--SEARCH odunc USING COVERING INDEX odunc_alis_karma (<expr>=?)
820
VM-steps: 3292
--- ayni esitlik, tarihin kendisi uzerinden sorulunca ---
QUERY PLAN
`--SCAN odunc
820
VM-steps: 6000831
--- aralik ---
QUERY PLAN
`--SCAN odunc
5744
VM-steps: 7046372
```

Üç sonuç üç ayrı şey söyler. Karma üzerinden sorulan eşitlik 3.292 adımda yanıtlandı —
ağaç dizinin 2.471 adımına yakın, aynı büyüklük sınıfında.

İkinci ölçüm modelin sınırını gösterir: aynı eşitlik, tarih sütunu üzerinden yazıldığında
dizin kullanılmadı. Gerçek bir karma dizinde bu dönüşümü motor kendisi yapar; kullanıcı
sıradan bir eşitlik yazar, motor arama anahtarının karmasını hesaplar. Modelde dönüşümü
sorguyu yazan üstlenmek zorundadır. Değişmeyen nokta şudur: **arama anahtarının karması
hesaplanabiliyorsa** eşitlik yanıtlanır.

Üçüncü ölçüm asıl ayrımdır. Aralık sorgusu, dizin var olduğu hâlde tam tablo taramasına
düştü ve 7.046.372 adım harcadı. Neden yapısaldır: karma fonksiyonu bilerek düzensizdir.
Birbirini izleyen iki tarihin karma değerleri dizinin iki ucunda olabilir. "15 Mart ile
21 Mart arası" diye bir aralığın karma karşılığı yoktur, çünkü aradaki değerlerin hangi
karmalara düştüğü hesaplanamaz — hepsi tek tek denenmedikçe.

Buradan karma dizinin kullanım alanı çıkar: yalnız eşitlikle sorgulanan, aralığı ve
sırası hiç istenmeyen sütunlar. Karşılığında kazandığı şey yerdir; uzun metin anahtarlar
sabit boyutlu bir karmaya indiğinde dizin küçülür. Ağaç dizinin sunduğu her şeyden
vazgeçilmesi karşılığında elde edilen bu kazanç, çoğu kurulumda tercih edilmez.

## Ters Dizin

Üçüncü aile başka bir soruyu hedefler. Ağaç dizin **değerin tamamını** anahtar sayar;
bu yüzden ön ek sorgularını yanıtlar ama değerin ortasındaki bir sözcüğü bulamaz.
Kütüphane kataloğunda konu başlıklarında arama yapmak tam olarak bu sorudur.

```sh
rm -f konu.db
sqlite3 konu.db <<'SQL'
CREATE TABLE kitap_konu (kitap_id INTEGER PRIMARY KEY, konu TEXT NOT NULL);
INSERT INTO kitap_konu (kitap_id, konu)
WITH RECURSIVE s(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM s WHERE n < 200000),
sozcuk(i, w) AS (VALUES (0,'gokbilim'),(1,'cografya'),(2,'tarih'),(3,'siir'),(4,'roman'),
                        (5,'atlas'),(6,'sozluk'),(7,'elkitabi'),(8,'denizcilik'),
                        (9,'mimarlik'),(10,'muzik'),(11,'felsefe'),(12,'kartografya'),
                        (13,'botanik'),(14,'mineraloji'),(15,'hukuk'),(16,'tiyatro'),
                        (17,'mantik'),(18,'iktisat'),(19,'anatomi'))
SELECT n, (SELECT w FROM sozcuk WHERE i = n % 20)       || ' ' ||
          (SELECT w FROM sozcuk WHERE i = (n/20) % 20)  || ' ' ||
          (SELECT w FROM sozcuk WHERE i = (n/400) % 20)
FROM s;
CREATE INDEX konu_agac ON kitap_konu(konu);
CREATE VIRTUAL TABLE konu_ters USING fts5(konu, content='kitap_konu', content_rowid='kitap_id');
INSERT INTO konu_ters(konu_ters) VALUES('rebuild');
SQL
sqlite3 konu.db <<'SQL'
PRAGMA case_sensitive_like = ON;
.print '--- agac dizini, sol ucu bagli kalip ---'
EXPLAIN QUERY PLAN SELECT count(*) FROM kitap_konu WHERE konu LIKE 'kartografya denizcilik%';
.stats vmstep
SELECT count(*) FROM kitap_konu WHERE konu LIKE 'kartografya denizcilik%';
.stats off
.print '--- ayni agac dizini, sol ucu acik kalip ---'
EXPLAIN QUERY PLAN SELECT count(*) FROM kitap_konu WHERE konu LIKE '%kartografya denizcilik%';
.stats vmstep
SELECT count(*) FROM kitap_konu WHERE konu LIKE '%kartografya denizcilik%';
.stats off
.print '--- ters dizin, ayni sozcuk obegi ---'
EXPLAIN QUERY PLAN SELECT count(*) FROM konu_ters WHERE konu_ters MATCH '"kartografya denizcilik"';
.stats vmstep
SELECT count(*) FROM konu_ters WHERE konu_ters MATCH '"kartografya denizcilik"';
SQL
```

```
--- agac dizini, sol ucu bagli kalip ---
QUERY PLAN
`--SEARCH kitap_konu USING COVERING INDEX konu_agac (konu>? AND konu<?)
500
VM-steps: 1518
--- ayni agac dizini, sol ucu acik kalip ---
QUERY PLAN
`--SCAN kitap_konu
1000
VM-steps: 801011
--- ters dizin, ayni sozcuk obegi ---
QUERY PLAN
`--SCAN konu_ters VIRTUAL TABLE INDEX 0:M1
1000
VM-steps: 2013
```

İlk ölçüm ağaç dizinin yapabildiğini gösteriyor: sol ucu bağlı kalıp bir aralığa çevrildi
ve 1.518 adımda yanıtlandı. İkinci ölçümde aynı dizin duruyor, aranan aynı sözcük öbeği,
tek fark kalıbın sol ucunun açılması — ve dizin devre dışı kaldı. Sorgu Başarımı konusunda
kurulan kural burada tekrar görünüyor: sol ucu açık bir kalıp, sıralı bir yapıda giriş
noktası bırakmaz.

**Ters dizin (inverted index)** bu duvarı yapıyı değiştirerek aşar. Anahtar olarak değerin
tamamını değil, değerin içinden çıkarılan her sözcüğü alır; her sözcüğün karşısında o
sözcüğü içeren kayıtların listesi durur. Sözcük öbeği araması iki listenin kesişimi ve
konum karşılaştırmasıyla yanıtlanır: 2.013 adım, tarama yolunun dört yüzde biri.

Bedeli vardır. Ters dizin, kaynak metnin sözcük sayısıyla orantılı büyür ve her metin
değişikliğinde birden çok sözcük girdisi güncellenir. Ayrıca sözcüklere ayırma kuralı
dile bağlıdır: kök bulma, ek ayırma ve büyük–küçük harf eşlemesi Türkçe için ayrı
ayarlanır. Bu ayarlar motorlar arasında en çok farklılaşan alandır.

## Aile Listesi ve Seçim Ölçütü

Yaygın dizin aileleri, yanıtladıkları soruya göre ayrılır. Ağaç tabanlı dizin sıra
gerektiren her soruyu karşılar. Karma tabanlı dizin yalnız eşitliği karşılar. Ters dizin
metin içindeki sözcüğü bulur. Bunların yanında iki aile daha sık geçer: çok az sayıda
farklı değer alan sütunlar için her değere bir bit örüntüsü tutan **bit eşlem dizini
(bitmap index)** ve iki boyutlu kutuları kapsayan dikdörtgenlerle örten **uzamsal dizin
(spatial index)**. İkincisi Veri Yapıları kursundaki k-d ağacıyla aynı sorunu çözer:
sıralaması tek boyutlu olmayan verinin aranması.

Seçim ölçütü tek cümlede toplanır: dizin türü, sütunun tipine göre değil **sorgunun
biçimine** göre seçilir. Aynı tarih sütunu, yalnız eşitlikle sorgulanıyorsa karma
tabanlı bir yapıya, aralıkla sorgulanıyorsa ağaç tabanlı bir yapıya uygundur. Hangi
türlerin bulunduğu motora özgüdür; değişmeyen, türü belirleyen sorunun sorgu tarafında
durmasıdır.

## Özet

- Dizinsiz bir yabancı anahtar sütunu, ana tablodaki her silmeyi çocuk tablonun taranmasına
  çevirir; ölçümde tek satırlık bir silme 600.013 adımdan 15 adıma indi.
- Ağaç tabanlı dizin girdileri sıralı tuttuğu için eşitlik, aralık, ön ek, en küçük–en
  büyük ve hazır sıra sorularının hepsini karşılar.
- Karma tabanlı dizin anahtarın karmasını sıralar; eşitliği ağaç dizine yakın maliyetle
  yanıtlar, aralık sorgusunda ise dizin kullanılamaz ve sorgu taramaya düşer.
- Ters dizin, değerin tamamını değil içindeki sözcükleri anahtar alır; sol ucu açık kalıpla
  801.011 adım süren arama, sözcük öbeği araması olarak 2.013 adımda yanıtlandı.
- Dizin türü sütunun tipine göre değil sorgunun biçimine göre seçilir; sıra istenmeyen bir
  sütunda ağaç dizinin sunduğu yeteneklerin bedeli ödenmiş ama karşılığı alınmamış olur.

## Sonraki Adım

Bu dersteki bütün dizinler tek sütunluydu ve tablonun bütün satırlarını kapsıyordu.
İki kısıtlama da gevşetilebilir. Bir dizin birden çok sütun taşıdığında, sütunların
yazıldığı sıra dizinin hangi sorguya yarayacağını belirler — aynı iki sütun iki ayrı
sırayla dizinlendiğinde ortaya iki farklı yapı çıkar. Bir dizin tablonun yalnız bir
bölümünü kapsadığında ise hem küçülür hem de kapsadığı bölümde daha ucuz çalışır. Sonraki
ders bu iki kararı ölçer: sütun sırasının üç ayrı sorgu üzerindeki etkisi ve koşullu bir
dizinin boyut ile plan tarafındaki kazancı.
