İndeksleme: dense/sparse indeks, B+ ağacı, hash ve seçim maliyeti
Kayıtlar diskte nasıl durur, indeks aramayı nasıl hızlandırır, B+ ağacına ekleme nasıl yapılır ve bir seçimin maliyeti nasıl hesaplanır.
Şimdiye kadar SQL sorgusunu yazdık ve sonucun ne olacağını konuştuk. Bu hafta soru değişiyor: veritabanı o sonucu ne kadar sürede buluyor? Cevap diskteki düzene ve indekslere bağlı. Sınavda bu konudan üç tip soru gelir: maliyet hesabı (ms cinsinden), B+ ağacına ekleme yapıp son ağacı çizme ve “hangi plan daha ucuz?” karşılaştırması.
Altın kurallar:
- Maliyetin birimi disk erişimi. CPU ihmal edilir. Bir blok okumak = transfer (), başka bir yere atlamak = seek ().
- Seek, transferden çok pahalı. Bu sayfada hep ms, ms kullanacağız.
- İndeks her zaman kazandırmaz. Çok kayıt eşleşiyorsa ikincil indeks, düz taramadan yavaş olabilir.
- B+ ağacında yaprak bölünürken anahtar yukarı kopyalanır, iç düğüm bölünürken yukarı taşınır.
Depolama: blok, kayıt, dosya
Disk, veriyi sabit boyutlu bloklar (block) halinde okur ve yazar. Bir kayıt (record) tek başına okunmaz; içinde bulunduğu blok komple belleğe gelir. Bu yüzden önemli sayılar şunlardır:
| Sembol | Anlamı |
|---|---|
| ilişkideki kayıt (tuple) sayısı | |
| blocking factor: bir bloğa sığan kayıt sayısı | |
| ilişkinin kapladığı blok sayısı |
Örnek: blok 4096 bayt, kayıt 200 bayt. . 10000 kayıt varsa blok.
Dosya düzenleri:
- Heap file: kayıtlar boş yer neredeyse oraya yazılır, sıra yok. Ekleme ucuz, arama pahalı.
- Sequential (sorted) file: kayıtlar bir arama anahtarına (search key) göre sıralı. İkili arama ve aralık sorguları mümkün, ama ekleme sırayı korumak için taşma bloklarına ihtiyaç duyar.
İndeks kavramları
İndeks (index), kitabın arkasındaki dizin gibidir: “anahtar değeri → kaydın yeri” çiftleri tutar. İndeksi kurduğumuz öznitelik(ler)e arama anahtarı (search key) denir. Dikkat: arama anahtarı, tablonun birincil anahtarı olmak zorunda değil.
| Tür | Tanım | Not |
|---|---|---|
| Dense index | Her arama anahtarı değeri için bir girdi | Her dosyada kurulabilir |
| Sparse index | Sadece bazı değerler için girdi (genelde blok başına bir) | Dosya o anahtara göre sıralı olmalı |
| Primary / clustering index | Dosya, indeksin anahtarına göre sıralı | Tablo başına en fazla bir tane |
| Secondary index | Dosya bu anahtara göre sıralı değil | Her zaman dense olmak zorunda |
Sparse indekste stu_id = 'S4471' arıyorsan, indekste S4471’den küçük ya da ona eşit en büyük girdiyi bulursun, o bloğa gidip sırayla ilerlersin. Bu sadece dosya sıralıysa çalışır. Secondary indeks sparse olamaz, çünkü kayıtlar dağınıktır ve “bu bloktan sonra devam et” diyemezsin.
Sayılarla: 10000 kayıt, 500 blok, bir indeks bloğuna 100 girdi sığıyor. Dense indeks 10000 girdi, yani 100 blok. Sparse indeks 500 girdi, yani 5 blok. Sparse daha küçük, dense ise bazı sorguları veriye hiç gitmeden cevaplar (örneğin “bu anahtar var mı?”).
Çok seviyeli indeks
İndeks büyüyünce kendisi de diskte aranacak bir dosya olur. Çözüm: indeksin üstüne sparse bir indeks daha kur, en üst seviye tek bloğa sığana kadar devam et. Buna çok seviyeli indeks (multilevel index) denir. Her seviyeden bir blok okunur, sonra veri bloğu. B+ ağacı bu fikrin dengeli ve güncellenebilir halidir.
B+ ağacı
B+ ağacı (B+ tree) veritabanlarının varsayılan indeksidir. Parametresi : bir düğümdeki en fazla işaretçi sayısı. Bir düğüm en fazla anahtar tutar.
| Düğüm | En az | En fazla |
|---|---|---|
| Yaprak (leaf) | anahtar | anahtar |
| İç düğüm (kök hariç) | çocuk | çocuk |
| Kök (yaprak değilse) | 2 çocuk | çocuk |
Özellikler:
- Bütün yapraklar aynı derinlikte. Ağaç her zaman dengeli.
- Gerçek kayıt işaretçileri sadece yapraklarda. İç düğümlerdeki anahtarlar sadece yol gösterir.
- Yapraklar soldan sağa zincirli (son işaretçi bir sonraki yaprağı gösterir). Aralık sorgusunda ilk yaprağı bulup zincir boyunca yürürsün.
Yükseklik sınırı, farklı anahtar için:
Gerçek sayılar: 4 KB blok, 32 baytlık anahtar, 8 baytlık işaretçi. eşitsizliğinden çıkar. Kabaca alırsak, bir milyon anahtar için . Bir milyon kayıt içinde arama en fazla 4 düğüm okur. İkili aramada bu sayı 20 civarındadır.
Arama: kökten başla. Düğümde aranan değerden büyük ilk anahtarın solundaki işaretçiyi izle; eşit ya da büyükse sağa geç. Yaprağa gelince anahtarı orada ara.
Ekleme ve bölünme (adım adım)
(en fazla 3 anahtar). Taşan yaprakta ilk anahtar solda kalır, gerisi yeni yaprağa gider, yeni yaprağın ilk anahtarı ebeveyne kopyalanır. Taşan iç düğümde ilk işaretçi solda kalır, aradaki anahtar ebeveyne taşınır (alt düğümde kalmaz).
Sırayla 10, 20, 30, 40, 50, 25, 35, 5, 45, 55 ekleyelim. Seviyeler / ile ayrıldı, en sağdaki yapraklar:
ekle 10 -> [10]
ekle 20 -> [10 20]
ekle 30 -> [10 20 30]
ekle 40 -> [30] / [10 20] [30 40] yaprak taştı, 30 kopyalandı
ekle 50 -> [30] / [10 20] [30 40 50]
ekle 25 -> [30] / [10 20 25] [30 40 50]
ekle 35 -> [30 40] / [10 20 25] [30 35] [40 50] sağ yaprak bölündü, 40 yukarı
ekle 5 -> [20 30 40] / [5 10] [20 25] [30 35] [40 50]
ekle 45 -> [20 30 40] / [5 10] [20 25] [30 35] [40 45 50]
ekle 55 -> [40] / [20 30] [50] / [5 10] [20 25] [30 35] [40 45] [50 55]
Son adımı aç: [40 45 50 55] taşar, [40 45] ve [50 55] olur, 50 köke gider. Kök [20 30 40 50] olur, 5 çocuk, sınırını aşar. İç düğüm bölünür: solda 3 işaretçi ([20 30]), 40 yeni köke taşınır, sağda [50]. Ağaç bir seviye uzadı. B+ ağacı aşağı doğru değil, yukarı doğru büyür. Bu yüzden yapraklar hep aynı derinlikte kalır.
TUZAK · 40 nerede? Son ağaçta 40 hem kökte hem yaprakta [40 45] var. Bu doğru: yaprak bölünmesinde kopyalanan değer yaprakta da durur. Ama iç düğüm bölünmesinde yukarı çıkan 40, ikinci seviyede tekrar yazılmaz. [20 30 40] / [50] yazan puan kaybeder.
Silme (kısaca)
Silmede yaprak minimum doluluğun altına düşerse önce kardeşten ödünç alınır (redistribution), olmuyorsa kardeşle birleştirilir (merge/coalesce) ve ebeveynden bir anahtar silinir. Birleşme köke kadar yayılabilir; kökün tek çocuğu kalırsa kök silinir ve ağaç bir seviye kısalır.
Hash ile indeksleme
Statik hash (static hashing): kova (bucket) ve bir hash fonksiyonu . Kayıt numaralı kovaya yazılır. Eşitlik araması tek kova okur; aralık sorgusu için hash işe yaramaz çünkü komşu değerler dağılır.
ile 12, 7, 21, 30, 16, 9, 43:
| Kova | Anahtarlar |
|---|---|
| 0 | 12, 16 |
| 1 | 21, 9 |
| 2 | 30 |
| 3 | 7, 43 |
Kova dolarsa taşma bloğu (overflow bucket) zincirlenir. Zincir uzadıkça arama yavaşlar. Veri büyüdükçe sabit kaldığı için statik hash bozulur.
Extendable hashing bu sorunu çözer: hash değerinin ilk bitine bakan bir dizin (directory) tutar. Bir kova taşınca sadece o kova ikiye bölünür; gerekirse dizin boyutu ikiye katlanır ( bir artar). Bütün dosyayı yeniden hash’lemek gerekmez.
Seçim (selection) maliyetleri
ya da için kullanılan yöntemler. Kitabın baskısına göre A1, A2 numaraları değişir; sınavda yöntemin adını ve formülünü bilmen yeter. : B+ ağacının yüksekliği. Örneklerde , .
| Yöntem | Maliyet | Örnek |
|---|---|---|
| Lineer tarama (linear scan) | ms | |
| Lineer, anahtarda eşitlik (ortalama) | ms | |
| İkili arama, dosya anahtara göre sıralı | ms | |
| Birincil B+ indeks, anahtarda eşitlik | ms | |
| Birincil indeks, anahtar olmayan eşitlik ( blok) | : ms | |
| İkincil indeks, eşleşen kayıt | : ms | |
| Birincil indeks, aralık ( blok) | : ms |
Mantık hep aynı: indeks ağacında her seviye rastgele bir okuma (seek + transfer). Birincil indekste veri sıralı olduğu için eşleşen kayıtlar ardışık bloklarda: bir seek, sonra transfer. İkincil indekste her eşleşen kayıt başka bir yerde olabilir, her biri ayrı seek ister.
TUZAK · ikincil indeks + çok eşleşme. 40 eşleşme ile ikincil indeks 176.3 ms, düz tarama 54 ms. Eşleşen kayıt sayısı arttıkça ikincil indeks hızla pahalanır. “İndeks varsa her zaman indeks kullanılır” cevabı yanlış.
Gerçek bir sistemde bunu görmek için örnek veritabanımızda (takes tablosu) SQLite’ın planına bakalım:
EXPLAIN QUERY PLAN SELECT * FROM takes WHERE course_id = 'MA101';
CREATE INDEX idx_takes_course ON takes(course_id);
EXPLAIN QUERY PLAN SELECT * FROM takes WHERE course_id = 'MA101';
SCAN takes
SEARCH takes USING INDEX idx_takes_course (course_id=?)
İndeks yokken tablo baştan sona taranıyor (SCAN). İndeksten sonra B+ ağacında arama yapılıyor (SEARCH). Sonuç iki durumda da aynı üç satır: S1, S3, S4. İndeks sonucu değil, yolu değiştirir.
Sık yapılan hatalar
| Yanlış düşünce | Doğrusu |
|---|---|
| yukarı yuvarlanır | Aşağı yuvarlanır (yarım kayıt sığmaz); yukarı yuvarlanır |
| Secondary index sparse olabilir | Olamaz, dosya o anahtara göre sıralı değil, hep dense |
| Bir tabloda birden çok clustering index | Dosya tek bir düzende sıralanabilir, en fazla bir tane |
| = düğümdeki anahtar sayısı | işaretçi sayısı; anahtar en fazla |
| İç düğüm bölünmesinde ortadaki anahtar iki yerde kalır | İç düğümde yukarı taşınır, sadece yaprak bölünmesinde kopyalanır |
| B+ ağacı yapraklardan aşağı büyür | Kök bölünür, ağaç yukarı büyür; yapraklar hep aynı derinlikte |
| İkili aramada sadece transfer sayılır | Her adım farklı bloğa atlar: |
| İndeks varsa her zaman daha hızlı | Çok eşleşen ikincil indeks, , taramadan yavaş olabilir |
| Hash aralık sorgusunu hızlandırır | Sadece eşitlik; aralık için B+ ağacı |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
A disk block holds 4096 bytes and each fixed-length record is 120 bytes (records do not span blocks). A relation has 17000 records. Compute the blocking factor and the number of blocks .
Cevabı göster
kayıt/blok. Kayıt bloklar arasında bölünmediği için aşağı yuvarlanır. blok. Blok sayısında yukarı yuvarlanır, yarım blok da bir bloktur.
A file sorted on stu_id has 30000 records, 30 records per block. How many entries does a sparse primary index on stu_id have (one entry per block)? How many does a dense index have?
Cevabı göster
Blok sayısı . Sparse indeks her blok için bir girdi tutar: 1000 girdi.
Dense indeks her arama anahtarı değeri için girdi tutar. stu_id anahtar olduğu için her kayıt ayrı değer: 30000 girdi.
A heap file has blocks. With ms and ms, what is the cost of a linear scan for salary > 50000? What is the average cost for an equality condition on a key attribute?
Cevabı göster
Koşul anahtar üzerinde değilse dosyanın tamamı okunur: ms.
Anahtar üzerinde eşitlikte ilk eşleşmede durulur; ortalama yarısı okunur: ms.
A file is sorted on the key course_id and has 1000 blocks, no index. Estimate the cost of course_id = 'CS305' using binary search ( ms, ms).
Cevabı göster
İkili arama blok okur ().
Her okuma rastgele bir yere gider, yani her blok bir seek + bir transfer: ms.
In a B+ tree with (at most 5 pointers per node), what are the minimum and maximum number of keys in a leaf, and the minimum and maximum number of children of a non-root internal node?
Cevabı göster
Düğümde en fazla anahtar var.
Yaprak en az anahtar tutar, en fazla 4.
Kök olmayan iç düğüm en az çocuk, en fazla 5 çocuk tutar.
Kök istisna: yaprak değilse en az 2 çocuk yeter.
Insert the keys 8, 5, 1, 7, 3 (in this order) into an empty B+ tree with (at most 2 keys per node). When a node overflows, the left node keeps keys (leaf) or pointers (internal). Give the final tree level by level.
Cevabı göster
8, 5: tek yaprak [5 8].
1: [1 5 8] taşar. Solda 2 anahtar [1 5], sağda [8]. Sağın ilk anahtarı 8 yukarı: kök [8].
7: 7 < 8 olduğu için sol yaprağa: [1 5 7] taşar, [1 5] ve [7] olur, 7 köke çıkar: kök [7 8], 3 çocuk (sınır 3, sorun yok).
3: [1 3 5] taşar, [1 3] ve [5], 5 yukarı: kök [5 7 8] olur, 4 çocuk, taşar.
İç düğüm bölünmesi: solda işaretçi [5], ortadaki 7 yukarı çıkar (iç düğümde yukarı çıkan anahtar alt düğümde kalmaz), sağda [8].
Sonuç: kök [7], ikinci seviye [5] [8], yapraklar [1 3] [5] [7] [8]. Yükseklik 3.
A B+ tree has and indexes search-key values. Give an upper bound on its height (number of levels from root to leaf).
Cevabı göster
En kötü durumda her düğüm yarı dolu: dallanma .
Yükseklik . , yukarı yuvarlayınca 4.
Yani bir anahtarı bulmak için en fazla 4 düğüm okunur.
A primary B+ tree index on the key inst_id has height . What is the cost of inst_id = 'I3' ( ms, ms)?
Cevabı göster
Kökten yaprağa düğüm, sonra kaydın bulunduğu 1 veri bloğu. Her biri rastgele erişim (seek + transfer). ms.
A relation has 500 blocks. A secondary B+ tree index on the non-key attribute dept_name has , and 200 records satisfy dept_name = 'CS', each in a different block. Compare the index plan with a linear scan ( ms, ms). Which is cheaper?
Cevabı göster
İkincil indekste her eşleşen kayıt ayrı bir blokta olabilir: ms.
Lineer tarama: ms. Tek seek, sonra ardışık okuma.
Çok kayıt eşleşiyorsa ikincil indeks taramadan pahalıdır. Optimizer bu yüzden indeksi her zaman kullanmaz.
Static hashing with 5 buckets and . Insert 23, 8, 15, 42, 31, 10. Which keys go to each bucket 0-4?
Cevabı göster
23 mod 5 = 3, 8 mod 5 = 3, 15 mod 5 = 0, 42 mod 5 = 2, 31 mod 5 = 1, 10 mod 5 = 0.
Kova 0: 15, 10. Kova 1: 31. Kova 2: 42. Kova 3: 23, 8. Kova 4 boş.
Kova kapasitesi 1 olsaydı 0 ve 3 numaralı kovalar taşardı (bucket overflow).
A dense index has 20000 entries and 100 index entries fit in one block. You build a multilevel index until the top level fits in one block. How many index levels are there, and how many block reads does a lookup need (including the data block)?
Cevabı göster
En alt seviye: blok.
Onun üstü: blok. Onun üstü: blok. Burada durulur.
3 indeks seviyesi, her seviyeden 1 blok okunur, sonra veri bloğu: 4 okuma.
A file is sorted on tot_cred and has a primary B+ tree index on it with . The query tot_cred >= 100 returns records stored in 40 consecutive blocks. Estimate the cost ( ms, ms).
Cevabı göster
İndeksle ilk uygun kayda in: ms.
Dosya bu öznitelikte sıralı olduğu için kalan kayıtlar ardışık bloklarda: bir seek ve 40 transfer, ms.
Toplam ms.
Doğrulama: B+ ağacı ekleme algoritması (yaprak ve iç düğüm bölünmesi) JS ile yazıldı, her ekleme sonrası ağaç basıldı; blocking factor, indeks seviye sayıları, hash kovaları ve tüm seçim maliyetleri node ile hesaplandı; indeksli/indekssiz sorgu planı node:sqlite EXPLAIN QUERY PLAN ile gösterildi (verify/SEN2104/12.mjs).