Sorgu işleme ve optimizasyon: join algoritmaları, denklik kuralları, tahmin
Bir sorgunun hangi adımlardan geçtiği, nested-loop/block/indexed/merge/hash join maliyetlerinin nasıl hesaplandığı ve optimizer'ın sorgu ağacını nasıl yeniden yazdığı.
Geçen hafta tek bir tablodan seçim yapmanın maliyetini hesapladık. Bu hafta sorgu birden çok tabloyu birleştiriyor. Aynı SQL sorgusu için onlarca farklı çalışma planı olabilir ve aralarında binlerce kat maliyet farkı çıkabilir. Optimizer’ın işi bunlardan ucuz olanı bulmak. Sınavda üç soru tipi var: join maliyeti hesapla, iki planı karşılaştır, sorgu ağacını denklik kurallarıyla yeniden yaz.
Altın kurallar:
- Maliyet = blok transferi + seek. Seek sayısını unutma, sorunun yarısı oradan gelir.
- Nested-loop türlerinde küçük ilişki dışa konur.
- Seçimi () olabildiğince aşağı it. Join’e giren satır sayısını azaltmak en büyük kazançtır.
- Denklik kuralı sonucu değiştirmez. Sadece maliyeti değiştirir.
Sorgu işlemenin adımları
- Parsing and translation: SQL’in sözdizimi kontrol edilir, tablo ve sütun isimleri doğrulanır, sorgu ilişkisel cebir ifadesine çevrilir.
- Optimization: eşdeğer cebir ifadeleri ve her işlem için algoritmalar düşünülür. Her plan için maliyet tahmin edilir (istatistiklerle), en ucuzu seçilir. Sonuç bir değerlendirme planıdır (query evaluation plan): her düğümünde hangi algoritmanın kullanılacağı yazan bir ağaç.
- Evaluation: plan çalıştırılır.
Maliyet ölçüsü: blok transferi ve seek için
Bu sayfada da ms, ms.
Dış sıralama (external sort-merge)
Tablo belleğe sığmıyorsa sıralama iki aşamada olur. = bellekteki buffer blok sayısı.
- Run oluşturma: bloğu oku, bellekte sırala, run olarak yaz. run çıkar.
- Birleştirme: her geçişte run birleştirilir (1 blok çıktı için). Geçiş sayısı .
Örnek: , . Run sayısı . Her geçişte 10 run birleşir: , 2 geçiş. Transfer . Son sonucun diske yazılması sayılmaz, çünkü genelde bir sonraki işleme doğrudan aktarılır.
Join algoritmaları
Gövdedeki örnek: = takes (, ), = student (, ). Join koşulu takes.stu_id = student.stu_id.
Nested-loop join
for each tuple t_r in r:
for each tuple t_s in s:
if (t_r, t_s) koşulu sağlıyorsa çıktıya yaz
En kötü durumda (her ilişkiye tek buffer):
takes dışta: transfer, seek. Süre ms, yaklaşık 2.4 dakika. student dışta: transfer. Burada dışın tuple sayısı çarpan olduğu için az tuple’lı olan dışta daha iyi.
En iyi durum: küçük ilişki belleğe tamamen sığıyorsa her ikisi bir kez okunur: transfer, 2 seek.
Block nested-loop join
Tuple tuple değil, blok blok eşleştir: dışın her bloğu için iç ilişki bir kez taranır.
takes dışta: transfer, 800 seek. student dışta: transfer, 200 seek. Bu sefer de küçük ilişki dışta kazanıyor.
buffer varsa dış ilişkiye blok ayrılır (bir iç, bir çıktı):
, student dışta: parça, transfer, 20 seek.
Indexed nested-loop join
İç ilişkide join özniteliği üzerinde indeks varsa iç ilişkiyi taramak yerine her dış tuple için indekste arama yapılır. = bir arama + eşleşeni getirme maliyeti.
takes dışta, student.stu_id üzerinde B+ ağacı: anahtarda eşitlik olduğu için blok erişimi. Erişim sayısı . Her biri seek + transfer olduğundan süre olarak bu, block nested-loop’tan pahalıdır. Indexed nested-loop, dış ilişki küçükken (örneğin önce sıkı bir uygulanmışsa) parlar.
Merge join
İki ilişki join özniteliğine göre sıralıysa ikisi paralel taranır, bir kez okunur:
Örnekte . Sıralı değillerse önce sıralama maliyeti eklenir. ile takes sıralaması 2000, student sıralaması ( run, 1 geçiş) transfer. Toplam . Sadece eşitlik ve doğal join (equi-join) için kullanılır.
Hash join
- Bölümleme (partitioning): iki ilişki aynı hash fonksiyonuyla parçaya bölünür. Eşleşebilecek tuple’lar aynı numaralı parçalara düşer.
- Her için belleğe alınır ve bellekte hash tablosu kurulur (build input), sonra okunup bu tabloda aranır (probe input).
Küçük ilişki build input olur; her belleğe sığmalı. Özyinelemeli bölümleme gerekmiyorsa:
Bölümlemede okuma + yazma , build/probe aşamasında bir okuma . Örnekte .
Özet karşılaştırma (takes ⋈ student)
| Algoritma | Transfer | Not |
|---|---|---|
| Nested-loop, takes dışta | 1 000 400 | en kötü |
| Block nested-loop, student dışta | 40 100 | |
| Block nested-loop, | 4 100 | |
| Indexed nested-loop, | 40 400 erişim | hepsi rastgele |
| Merge join + iki sıralama | 2 800 | sonuç sıralı çıkar |
| Hash join | 1 500 | eşitlik join’i |
TUZAK · formüllerde mi mi? Basit nested-loop’ta çarpan dışın tuple sayısı , block nested-loop’ta dışın blok sayısı . Indexed nested-loop’ta yine . Sınavda en sık kayıp puan buradan çıkar.
Denklik kuralları (equivalence rules)
İki cebir ifadesi her veritabanı örneğinde aynı sonucu veriyorsa eşdeğerdir. Optimizer’ın kullandığı başlıca kurallar:
| Kural | İfade |
|---|---|
| Seçim kaskadı | |
| Seçim değişmeli | |
| Projeksiyon kaskadı | , ise |
| ve | |
| Join değişmeli | |
| Join birleşmeli | |
| Seçimi aşağı itme | , sadece ‘in özniteliklerini kullanıyorsa |
| Projeksiyonu aşağı itme | , hem ‘deki hem ‘daki öznitelikleri içermeli |
| Küme işlemleri | ve değişmeli ve birleşmeli; bunlara ve işlemine dağılır |
Join değişmeliliği sütun sırasını değiştirir, ama ilişkide sütunlar isimle tanımlandığı için sonuç aynı ilişkidir.
Sezgisel optimizasyon (heuristic optimization)
Tüm planları denemek pahalı. Sezgisel kurallar çoğu durumda iyi plan verir:
- Konjonktif seçimleri kaskadla parçala.
- Her seçimi ağaçta olabildiğince aşağı it.
- En kısıtlayıcı (en az satır bırakan) seçim ve join’i önce yap.
- + çiftini ‘a çevir. Kartezyen çarpımdan kaç.
- Projeksiyonu erken yap, gereksiz sütunları at. Join için gereken sütunları tutmayı unutma.
Örnek: sorgu ağacını dönüştürmek
Örnek üniversite veritabanında: “CS bölümündeki öğrencilerden 3 kredilik bir ders almış olanların adları.” course tablosunda da dept_name olduğu için onu c_dept diye yeniden adlandırıyoruz; yoksa doğal join öğrencinin bölümüyle dersin bölümünü de eşitlemeye çalışır.
Saf çeviri:
Kaskad + seçimi aşağı itme + projeksiyonu erken yapma:
ara sonuç boyutları (örnek DB) saf plan optimize plan
student ⋈ takes ⋈ course 14 -
σ dept_name='CS'(student) - 3
σ credits=3(course) - 4
takes ⋈ σ(course) - 5
sonuç Ada Ada
İki plan aynı sonucu (sadece Ada) verir. Saf plan önce 14 satırlık bir join üretip sonra eliyor; optimize plan join’e 3 ve 5 satırla giriyor. Gerçek boyutlarda bu fark milyonlarca satırdır.
TUZAK · doğal join ve aynı isimli sütun. Yeniden adlandırmadan
student ⋈ takes ⋈ courseyazarsandept_namede eşitlenir ve örnek veritabanında 14 yerine 9 satır çıkar: öğrencinin kendi bölümü dışından aldığı dersler kaybolur. Bu bir optimizasyon değil, yanlış sorgudur.
İstatistikler ve boyut tahmini
Optimizer planın maliyetini tahmin etmek için katalogdaki istatistikleri kullanır: , , , ve , yani ‘de ‘nın farklı değer sayısı.
| İfade | Tahmini satır sayısı |
|---|---|
| (bağımsızlık varsayımı) | |
| , ortak öznitelik ‘nin anahtarı | ; yabancı anahtarsa tam |
| , ortak öznitelik anahtar değil |
Örnek: öğrenci, . dept_name = 'CS' tahmini . tot_cred 0 ile 240 arasında ise tot_cred <= 60 tahmini . İkisi birden: , yani yaklaşık 63 satır.
Tahmin bir varsayımdır. Örnek veritabanımızda student 7 satır ve COUNT(DISTINCT dept_name) = 3 (NULL sayılmaz). Tahmin , gerçekte CS’de 3 öğrenci var. Dağılım eşit değilse tahmin sapar; gerçek sistemler bu yüzden histogram tutar.
Sık yapılan hatalar
| Yanlış düşünce | Doğrusu |
|---|---|
| Nested-loop’ta | Tuple tabanlıda ; block nested-loop |
| Büyük ilişki dışta olsun | Nested-loop türlerinde küçük ilişki dışta |
| Seek’i atlamak | NLJ’de , BNLJ’de seek; seek transferden 40 kat pahalı |
| bufferın hepsi dış ilişkiye | : bir blok iç ilişki, bir blok çıktı |
| Dış sıralamada geçiş sayısı | Birleştirmede yol: |
| Merge join her zaman | Sadece zaten sıralıysa; değilse sıralama maliyeti eklenir |
| Hash join’de büyük ilişki build input | Küçük ilişki build input, parçaları belleğe sığmalı |
| Seçimi join’in istediğin koluna indir | Sadece koşuldaki tüm öznitelikler o kolda varsa |
| Projeksiyonu erken yaparken join sütununu at | Join özniteliği üst seviyede gerekiyorsa tutulmalı |
| yerine ile join tahmini | Daha sıkı olan küçük değer: büyük ‘ye böl |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
Put the steps of query processing in the correct order: optimization, parsing and translation, evaluation.
Cevabı göster
Önce SQL sözdizimi kontrol edilir ve ilişkisel cebir ifadesine çevrilir (parsing and translation).
Sonra optimizer eşdeğer planlar arasından tahmini en ucuzunu seçer (optimization).
En son evaluation engine seçilen planı çalıştırır ve sonucu döndürür.
Relation has tuples in blocks; has tuples in blocks. Using a simple (tuple) nested-loop join with as the outer relation and only one buffer block per relation (worst case), give the number of block transfers and seeks.
Cevabı göster
Dış ilişkinin her tuple'ı için iç ilişkinin tamamı okunur: .
Dış ilişkinin kendisi bir kez okunur: . Toplam 360300 transfer.
Seek: her dış tuple için iç ilişkiye bir seek () ve dış ilişkinin her bloğu için bir seek (): .
Same () and (). For a block nested-loop join (worst case, one buffer block each), compute the block transfers with as outer and with as outer. Which should be the outer relation?
Cevabı göster
Formül: transfer, seek. dış: transfer, 600 seek. dış: transfer, 120 seek.
Çarpım kısmı aynı, fark ikinci terimde ve seek sayısında. Küçük ilişki dışta olmalı: .
Block nested-loop join of () and () with memory buffers, as outer. Give block transfers and seeks.
Cevabı göster
blok dış ilişkiye ayrılır (1 blok iç ilişki, 1 blok çıktı).
Dış ilişki parçada okunur. Her parça için iç ilişki bir kez taranır.
Transfer: . Seek: .
Tek bufferlı versiyondaki 18060 transfer 960'a indi.
Indexed nested-loop join: (, ) is the outer relation and has a B+ tree index on the join attribute. Each index lookup plus fetching the matching tuple costs block accesses. What is the total number of block accesses? If instead (, ) is outer and has an index with the same , what is the cost?
Cevabı göster
Formül: . dış: . dış: .
Indexed nested-loop'ta dış ilişkinin tuple sayısı belirleyici. Az tuple'lı ilişkiyi dışa koymak ucuz.
Both () and () are already sorted on the join attribute. How many block transfers does a merge join need (ignore the output)?
Cevabı göster
İki sıralı ilişki aynı anda birer kez taranır: transfer.
Join değerinde çok sayıda tekrar yoksa (aynı değerli tuple'lar belleğe sığıyorsa) ek okuma olmaz.
External sort-merge of a relation with blocks using buffer blocks. How many initial runs are created, how many merge passes are needed, and what is the total number of block transfers (do not count writing the final result)?
Cevabı göster
Başlangıç run sayısı: .
Her geçişte run birleşir. Geçiş sayısı ; , yani 3 (43 → 8 → 2 → 1).
Transfer: .
Run oluşturma 1 okuma + 1 yazma, her geçiş 1 okuma + 1 yazma, son geçişin yazması sayılmaz.
For () and (), both unsorted, compare a hash join (no recursive partitioning, ignore partially filled blocks) with sort + merge join where sorting costs 2100 and sorting costs 300 transfers. Which is cheaper?
Cevabı göster
Hash join: iki ilişki bölümlenirken okunup yazılır (), sonra build/probe aşamasında bir kez daha okunur (): .
Sort-merge: .
Hash join daha ucuz. Ama sonucun join anahtarına göre sıralı istenmesi gibi durumlarda merge join avantaj kazanabilir.
Relation instructor has tuples and . Estimate the size of . Also estimate (salary in thousands) if salaries range uniformly from 10 to 90.
Cevabı göster
Eşitlik: değerlerin eşit dağıldığı varsayılır, .
Karşılaştırma: .
takes has 6000 tuples and student has 1500 tuples. takes.stu_id is a foreign key referencing the primary key student.stu_id (NOT NULL). Estimate the size of .
Cevabı göster
Ortak öznitelik student için anahtar ve takes.stu_id yabancı anahtar. Her takes tuple'ı tam olarak bir student tuple'ıyla eşleşir.
Sonuç tam olarak tuple. Genel kural: ortak öznitelik 'nin anahtarıysa join en fazla tuple verir.
On the sample university database, rewrite by pushing the selection down. How many tuples does the result have?
Cevabı göster
dept_name sadece student'ta var, bu yüzden seçim join'in student koluna inebilir: .
EE öğrencileri: S3 (Cem) ve S6 (Firat). S6 hiç ders almamış. S3'ün üç kaydı var: EE210, MA101, CS101.
Sonuç 3 tuple. Join'e 7 yerine 2 student tuple'ı girer.
has 1000 tuples, has 3000 tuples, and . is not a key of either relation. Estimate the size of .
Cevabı göster
İki tahmin: ve .
Küçük olanı (daha sıkı tahmin) alınır: . Bu, büyük değerine bölmek demek.
Doğrulama: Tüm join, dış sıralama ve boyut tahmini sayıları node ile formüllerden hesaplandı; denklik kuralları (kaskad, seçimi aşağı itme, join değişme/birleşme) ve sorgu ağacı dönüşümü örnek üniversite DB'sinde hem ilişkisel cebir (JS küme işlemleri) hem node:sqlite ile çalıştırılıp sonuçların aynı olduğu kontrol edildi (verify/SEN2104/13.mjs).