Kapittel 7 · Forelesning 13 · Del 2

Spørringsutføring

Hvordan databasen velger måter å hente rader på (aksessmetoder), sorterer store datamengder, og kombinerer tabeller (join-algoritmer) — hvordan en SELECT faktisk blir kjørt mot disk.

Bratsbergs notater 12–14 Forelesning 13 queries.pdf
01 · Helhetsbilde

Samme SQL — tre planer, tre kostnader

En spørring som SELECT name FROM Employee WHERE depno = 5 ser uskyldig ut. Men databasen kan kjøre den på 3–4 helt forskjellige måter, og kostnadsforskjellen kan være 1000×. Kapittel 6 svarte på hvor dataen ligger; kapittel 7 svarer på hvordan vi henter og kombinerer den — og hvilken plan som vinner avhenger av hva som finnes av indekser, hvor mange rader som matcher, og hvor mye RAM som er tilgjengelig.

Samme spørring kjørt på tre måter — radikalt forskjellig kostnad SELECT name FROM Employee WHERE depno = 5 PLAN A · FULL SCAN les hele heapfilen … 1961 blokker leses … ~1961 I/O PLAN B · INDEKS-LOOKUP unclustered B+ på depno indeks ↓ RID → tilfeldig blokk ~3 + treff·1 I/O PLAN C · CLUSTERED B+ range-scan på sorterte blader ← bladlenker → 3 nivå tre + få sammenhengende blokker ~3 + få I/O Optimizeren regner kostnaden for alle gyldige planer — og velger den billigste. Plan B vinner ved få treff. Plan A vinner ved mange treff. Plan C vinner ved range-spørringer.
Tre lovlige planer for samme SQL. Hovedlæringen i kap 7 er å regne ut hvilken plan som vinner — og hvorfor.
4 aksessveier
4 join-algoritmer (J1–J4)
I/O alt måles i blokker
Mental modell

I kap 6 ble vi kjent med lageret (heap, B+-tre, hash). I kap 7 er vi logistikkavdelingen: gitt at vi skal levere et bestemt sett rader, hva er den billigste rutevalget? Optimizeren er sjefen som velger ruten; executoren er sjåføren som kjører den.

02 · Pipelinen

Hva skjer mellom SQL og resultat?

En SQL-tekst går gjennom fire ledd før den blir til byter på pulten din. Kapittel 7 fokuserer på de to midterste — optimizer og executor — og hvordan de bruker statistikk og aksessmetoder for å finne raskest mulig vei til svaret.

SQL-pipeline: parser, optimizer, executor, lagring 1 · PARSER syntaks → tre slår opp tabeller i SQL-dictionary 2 · OPTIMIZER ★ velger billigste plan leser statistikk aksess + join + sort 3 · EXECUTOR ★ kjører plantreet aksessmetoder, sort, join, filter, project 4 · STORAGE heap · B+ · hash leverer blokker via buffer pool (kap 6) ★ KAP 7 ★ KAP 7 SELECT name FROM Employee … σ depno=5 π name (Employee) IndexLookup(depno=5) → Project(name) [blokk B17] [blokk B42] input algebra-tre fysisk plan blokker Optimizerens jobb: blant alle lovlige planer, finn den med lavest forventet I/O. Den må ha kostnadsmodell + statistikk fra dictionaryet for å gjette riktig. Optimizeren genererer mange planer … P1 P2 P3 ★ P4 P5 ★ velger den billigste
SQL-pipelinen. Parser og storage er kjent fra før (kap 3 og 6). Kapittel 7 lever i de to stjernemerkede leddene.
03 · Reisen gjennom kapittelet

Hva du tar med deg fra hver del

Innholdssiden er åtte korte seksjoner. Tenk på dem som verktøy som legges til verktøykassen — du trenger alle fire når du skal regne på en spørring på eksamen.

Reisen gjennom kapittel 7 — fire steg til kostnadsregning §02–03 Aksess filscan indeksscan range-scan indeks-lookup §04 Sortering external merge sort når RAM < data 2b·(1+passes) §05–06 Joins J1 nested loop J2 index nl J3 sort-merge J4 hash §07 EXPLAIN statistikk kostnadsmodell EXPLAIN-output type · key · rows «Hvordan finne radene?» «Hva om de må sorteres?» «Hvordan kombinere tabeller?» «Hva valgte den?» SLUTTPRODUKT — PLANEN Når du regner på en SQL-spørring på eksamen, går du gjennom hele verktøykassen i tur: 1) hvilken aksessvei henter rett delmengde billigst? → 2) trenger jeg å sortere? 3) hvilken join-algoritme passer for tabellstørrelsene? → 4) hva ville EXPLAIN sagt? Summen av blokk-aksesser i hvert ledd = totalkostnaden for planen.
Kapittelet bygger opp én verktøykasse, ett konsept om gangen. På eksamen brukes alle fire — i nettopp denne rekkefølgen.

§02–03 · Aksess

Hvor henter vi rader?
Fire aksessveier × fire lagringsalternativer = en tabell over kostnader for samme spørring.

§04 · Sortering

Når RAM ikke holder.
External merge sort med initial-pass + merge-passes. Formelen 2b·(1+passes) regner total I/O.

§05–06 · Joins

Hvordan kombineres to tabeller?
J1 (nested loop) i detalj med blokk-buffer, J2–J4 konseptuelt.

Slides og kompendium

Hovedkilde: queries.pdf + Bratsbergs kompendium kap. 12–14. Innholdssiden under er bygd som en gjennomgang — figurer og quizzer underveis, ikke bare på slutten.

04 · Læringsmodul

Utforsk kapittelet

Innholdssiden er bygget som en gjennomgang med interaktive figurer og innebygde sjekkspørsmål mellom hver del. Bruk «Neste steg»-knappene i animasjonene for å følge merge sort og nested loop trinn for trinn.

05 · Nøkkelbegreper

Vokabular du kunne

BegrepForklaring
AksessveiMåten executoren henter rader på: filscan (les hele tabellen), indeksscan, range-scan, indeks-lookup.
Blocking factor (bf)Antall rader som får plass i én blokk: bf = ⌊B/R⌋ (B = blokkstørrelse, R = recordstørrelse). Bestemmer hvor mange blokker tabellen krever: b = ⌈|R|/bf⌉.
Selektivitetsfaktor (sf)Andelen rader som overlever et predikat — mellom 0 og 1. Likhet: sf = 1/V(A,R). Range: sf = (high−c)/(high−low). AND multipliserer, OR summerer (med korreksjon).
V(A, R)Antall distinkte verdier av attributt A i tabell R. Brukes til å estimere selektivitet for likhet og antall matcher pr. ytre rad i en join.
StatistikkAntall rader, antall blokker per tabell/indeks, B+-tre-høyde, lavest og høyest forekommende verdi (lowkey/highkey), histogrammer (verdifordelinger). Optimizeren bruker dette til kostnadsestimat.
External merge sortSortering på disk når dataene ikke får plass i RAM. To faser: en initialfase og deretter merge-passes som flettes parvis. Variablene n_R, d_M og antall passes forklares på innholdssiden.
Nested loop join (J1)For hver rad i ytre tabell: scan indre tabell. Block nested loop bruker bufferblokker for å laste flere rader om gangen. Kost: b_R + ⌈b_R/(n_B−2)⌉·b_S.
Index nested loop (J2)Ytre tabell scannes; for hver rad slås matchende rader i indre tabell opp via en indeks. Kost (clustered): b_R + |R|·(h_S+1).
Sort-merge join (J3)Sorter begge sider på join-attributtet (kolonnen vi joiner på), gå gjennom dem med to pekere som flytter seg parallelt. Kost: sortkost(R) + sortkost(S) + b_R + b_S.
Partition-hash join (J4)Hash begge tabeller på join-attributtet; join hver partisjon for seg når dataene får plass i RAM. Kost: 3·(b_R + b_S).
Filter pushdownUtfør WHERE-predikater så tidlig som mulig i planen — gjerne før join. Reduserer størrelsen på mellomresultater og dermed antall passes over indre tabell i nested loop joins.
EXPLAINMySQL-kommando som viser planen optimizeren har valgt — kolonnene type, key, rows, Extra.
06 · Test deg selv

Helhetsspørsmål

Hurtig sjekk på de viktigste konseptene. Den fyldige sluttquizen ligger på innholdssiden.

Spørsmål 1 · Lett
Hva er forskjellen på en optimizer og en executor i et DBMS?
Optimizeren velger en utføringsplan basert på statistikk og kostnadsestimater. Executoren kjører den valgte planen ved å kalle aksessmetoder mot lagring og kombinere resultater.
Spørsmål 2 · Lett
Nevn aksessveiene som pensum behandler.
Filscan (tabellscan), indeksscan, range-scan, indeks-lookup.
Spørsmål 3 · Middels
Hvorfor er clustered versus unclustered indeks så avgjørende ved range-spørringer?
Clustered = radene ligger fysisk i nøkkelorden, så et range-treff blir et sammenhengende blokk-scan. Unclustered = radene er spredt; hvert treff kan bety følging av en RID-peker (Record IDentifier — peker til en rad i heap-filen) og en ny blokk-aksess. Ved bredt treff blir unclustered ofte verre enn full scan.
Spørsmål 4 · Middels
I external merge sort: hva er totalformelen for I/O og hvorfor multipliseres b med 2?
Total I/O (antall blokk-lesinger/skrivinger) = 2b · (1 + #merge-passes). 2-tallet kommer av at hvert pass både leser og skriver hele datasettet — initial-passet inkludert.
Spørsmål 5 · Vanskelig
b = 1024 blokker, n_B = 5 buffer. Hva er n_R, d_M og antall mergepasser?
n_R = ⌈1024/5⌉ = 205. d_M = 4. Antall passes = ⌈log₄(205)⌉ = 4. Total I/O = 2·1024·(1+4) = 10 240 blokker.
Spørsmål 6 · Lett
Hvilken tabell bør være ytre i en block nested loop join?
Den minste. Antall passer over indre tabell er ⌈|ytre|/(n_B−2)⌉, og det vil vi minimere.
Spørsmål 7 · Middels
Department har 5 blokker, Employee har 1000 blokker, buffer 5. Beregn block nested loop med Department ytre.
5 + ⌈5/3⌉·1000 = 5 + 2·1000 = 2005 blokker. Med Employee ytre: 1000 + ⌈1000/3⌉·5 = 2670. Department ytre vinner.
Spørsmål 8 · Lett
Hvilke fire join-algoritmer (J1–J4) nevner pensum?
J1 nested loop, J2 single-loop / index nested loop, J3 sort-merge, J4 partition-hash. Pensum behandler J1 i detalj og nevner de tre andre konseptuelt.
Spørsmål 9 · Middels
Hvilken statistikk lagrer SQL-dictionaryet for et B+-tre, og hva brukes den til?
Trehøyde, LowKey (laveste forekommende nøkkelverdi), HighKey (høyeste) og antall blokker. Optimizeren bruker dette til å estimere antall blokk-aksesser for indeks-lookup, range-scan og indeksscan, og til å velge billigste plan. SQL-dictionaryet (databasens metadata-katalog) er der disse tallene lagres.
Spørsmål 10 · Vanskelig
En unclustered B+-indeks finnes på salary. Spørringen er WHERE salary > 0. Optimizeren velger full table scan. Forklar.
Predikatet matcher alle radene. Med unclustered indeks blir kostnaden antall treff × en blokk pr. RID = opp mot 100 000 blokker for 100 000 rader. Full scan = ~1961 blokker. Optimizerens kostnadsmodell ser at indeksen er verre selv om den teknisk er anvendelig.
07 · Pensumeksamen

Flashcards — hele pensum, fra Kap 1 til Kap 8

Et bredt kortdekk som dekker både Del 1 (relasjonsmodellen, SQL, ER, normalformer) og Del 2 (lagring, queries, transaksjoner). Tanken er enkel: kan du alle kortene, har du skjønt absolutt alt i pensum. Klikk kortet for å snu det. Bruk piltastene til å bla, S for å shuffle, eller R for å resette rekkefølgen. Spørsmålene er i samme tone og vanskelighet som øvingseksamenene, men ingen oppgave er kopiert direkte — de tester samme begreper og regneferdigheter på ferske vinklinger.

Tastatur: forrige · neste · Enter/Space snu · S shuffle · R reset
1 / 0
Kap. 1 — ACID Lett

Hva står hver bokstav i ACID for, og hvilken DBMS-mekanisme er hovedansvarlig for hver av dem?

A — Atomicity: alt eller ingenting. Mekanisme: UNDO-logging + abort/rollback.

C — Consistency: databasens integritetsregler holder før og etter. Mekanisme: integritetssjekk (constraints, triggere) + at hver transaksjon er korrekt skrevet.

I — Isolation: samtidige transaksjoner ser ut til å kjøre etter hverandre. Mekanisme: concurrency control (2PL eller MVCC).

D — Durability: committed effekter overlever krasj. Mekanisme: WAL (write-ahead log) + force-log-at-commit.

Merk at en kortvarig serialiserbar utførelse kan være «konsistent» selv om et enkelt mellomtilstand bryter constraints — viktig at C måles ved commit, ikke midt i en transaksjon.

Pensum: Kap. 8 — ACID

Kap. 1 — Tre-skjema-arkitektur Middels

Beskriv ANSI/SPARC tre-skjema-arkitekturen og hva hvert nivå isolerer.

Tre lag mellom bruker og bits:

Eksternt skjema (view): hva en bestemt brukergruppe ser. En tabell kan være delvis skjult, et JOIN-resultat kan være eksponert som «én tabell», kolonner kan renames.

Konseptuelt skjema: den logiske, hele beskrivelsen — alle tabeller, kolonner, constraints, integritetsregler. Dette er det DDL-en din bygger.

Internt skjema: hvordan dataene faktisk er lagret — heap eller B+-tre, hvilke indekser finnes, blokkstørrelse, partisjonering.

Poenget er data-uavhengighet:
Logisk uavhengighet — endre konseptuelt skjema (legg til en kolonne) uten å bryte views.
Fysisk uavhengighet — endre interne strukturer (legg til indeks, repartisjoner) uten å endre SQL-en.

Pensum: Kap. 1 — DBMS-arkitektur

Kap. 1 — DBMS-komponenter Lett

Nevn de fire hovedkomponentene som behandler en SQL-spørring fra tekst til byter, i rekkefølge.

1. Parser: sjekker syntaks, slår opp tabeller og kolonner i SQL-katalogen, lager et logisk algebra-tre.

2. Optimizer: genererer kandidatplaner, beregner forventet kostnad ved hjelp av statistikk, velger den billigste planen. Eier kap. 7.

3. Executor: utfører den valgte planen — kaller aksessmetoder, kjører join-algoritmer, sorterer, projiserer.

4. Storage manager: oversetter blokkforespørsler til faktiske disk-I/O, går via buffer pool. Eier kap. 6.

I tillegg: concurrency control og recovery manager jobber parallelt over alle disse (kap. 8). SQL-katalogen (data dictionary) leses av parser og optimizer.

Pensum: Reisen gjennom DBMS-en

Kap. 2 — Nøkler Middels

Skill mellom supernøkkel, kandidatnøkkel og primærnøkkel. Kan en relasjon ha flere kandidatnøkler?

Supernøkkel: en mengde attributter som unikt identifiserer hver tuppel. Kan inneholde overflødige attributter (f.eks. {SSN, navn} er supernøkkel hvis SSN er det).

Kandidatnøkkel: en minimal supernøkkel — fjerner du ett attributt mister du unikheten. {SSN} er kandidatnøkkel, {SSN, navn} er ikke.

Primærnøkkel: én valgt kandidatnøkkel. DBMS-en lager typisk en clustered B+-indeks på den, og NULL er forbudt.

En relasjon kan ha flere kandidatnøkler (f.eks. både SSN og e-post er unike). Alle kandidatnøkler har samme egenskap (minimal+unik), men bare én er primærnøkkel — de andre kalles ofte alternate keys.

Pensum: Kap. 2 — Relasjonsmodellen

Kap. 2 — Fundamentale RA-operatorer Lett

Hvilke seks operatorer er fundamentale i relasjonsalgebra, og hvilke er avledede?

Fundamentale (6): σ (selection), π (projection), ∪ (union), − (set difference), × (cross product), ρ (rename).

Avledede: ∩ (intersection) = R − (R − S). Theta-join og natural join (⋈) = σ over ×. Divisjon (÷) kan uttrykkes via ×, − og π.

Merk: outer joins (⟕, ⟖, ⟗) er heller ikke fundamentale — de utvider natural join med tupler som ikke matcher, fylt ut med NULL.

Pensum: Kap. 2 — Relasjonsalgebra

Kap. 2 — RA tuppeltelling Middels

Tabellene R(a, b) har 4 tupler og S(b, c) har 5 tupler. Det er 3 tupler i R med en b-verdi som finnes i S, og hver av de 5 S-tuplene har én matchende R-tuppel. Hvor mange tupler er det i resultatet av R ⋈ S (natural join på b)?

  • A 3
  • B 5
  • C 8
  • D 20

Riktig: B — 5 tupler.

Natural join produserer én resultattuppel per (R, S)-par som matcher på b. Hver av de 5 S-tuplene har eksakt én matchende R-tuppel, så antallet par = 5.

(Hvis flere R-tupler hadde delt samme b-verdi som en S-tuppel, hadde tallet vært høyere — generelt: summer over hver delt b-verdi v antallet R-tupler med b=v ganget med antallet S-tupler med b=v.)

A (3) teller antall R-tupler som kan matche, men ikke selve parene. C (8) er 3 + 5 — typisk hvis man tror join er en union. D (20) er |R| × |S|, kartesisk produkt uten matching — feil med mindre alle b-verdier samsvarer.

Pensum: Kap. 2 — Relasjonsalgebra

Kap. 2 — Set-operasjoner Lett

Hva er kravet for at R ∪ S (union i RA) er definert?

R og S må være union-kompatible: samme antall attributter, og de korresponderende attributtene må ha samme domene (datatype).

Dette er strengere enn SQL-ens UNION, som krever samme antall kolonner og kompatible typer (typeparing). I RA er kolonnenavnene ikke nødvendigvis like — det er posisjonen som teller.

Samme krav gjelder for R − S og R ∩ S. (Cross product R × S krever ikke union-kompatibilitet — den lager bare en bredere relasjon.)

Pensum: Kap. 2 — Set-operasjoner

Kap. 2 — Outer join Middels

Forklar left outer join (⟕) i RA, og hva som skjer med tupler uten match.

R ⟕ S = alle resultater fra R ⋈ S, pluss hver tuppel fra R som ikke matchet noen tuppel i S — utvidet med NULL i alle S-attributter.

Symmetrisk: right outer join (⟖) bevarer alle S-tupler. Full outer join (⟗) bevarer alle fra begge sider.

Brukstilfelle: «vis alle ansatte og deres avdeling, også de uten avdeling». Ved natural join faller de uten match ut — outer join holder dem inne.

I SQL: LEFT JOIN, RIGHT JOIN, FULL OUTER JOIN. NULL fra outer join er ikke det samme som NULL i originaldataene — semantisk skille som av og til betyr noe i aggregatfunksjoner (f.eks. COUNT(*) teller NULL-rader, COUNT(col) ignorerer NULL).

Pensum: Kap. 2 — Outer join

Kap. 2 — Divisjon Vanskelig

Hva regner divisjon (R ÷ S) ut, og hva er det klassiske brukstilfellet?

R ÷ S finner de verdiene i R som er paret med alle verdier i S.

Definisjon: La R(A, B) og S(B). Resultatet er en relasjon over A der hver a-verdi i resultatet oppfyller: for hver b i S finnes (a, b) i R.

Klassisk brukstilfelle: «finn studenter som har tatt alle kurs i en bestemt liste». R = (student, kurs) takings-tabellen, S = listen av påkrevde kurs. R ÷ S = studenter som har tatt alle.

I SQL uttrykkes dette typisk med dobbelt NOT EXISTS: «ingen kurs i S som studenten ikke har tatt». Det er den samme logikken — universell kvantor utrykt som dobbel negasjon av eksistens.

Pensum: Kap. 2 — Divisjon

Kap. 3 — NULL-semantikk Middels

Hva er resultatet av NULL = NULL i SQL, og hvordan håndteres NULL i WHERE versus i aggregatfunksjoner?

NULL = NULL evalueres til UNKNOWN, ikke TRUE. SQL bruker tre-verdig logikk (TRUE / FALSE / UNKNOWN). For å sjekke om en kolonne er NULL: col IS NULL.

WHERE beholder bare rader der predikatet er TRUE. UNKNOWN-rader filtreres bort, akkurat som FALSE.

Aggregatfunksjoner ignorerer NULL — bortsett fra COUNT(*) som teller alle rader. COUNT(col), SUM(col), AVG(col) hopper over NULL-verdier. Konsekvens: SUM(x) / COUNT(x)AVG(x) hvis det er NULL i x.

Ekstra felle: NOT IN (subquery) kan returnere ingen rader hvis subqueryen inneholder NULL — fordi x NOT IN {a, NULL} blir x ≠ a AND x ≠ NULL, og det siste er UNKNOWN. Bruk NOT EXISTS i stedet.

Pensum: Kap. 3 — NULL og 3-verdig logikk

Kap. 3 — GROUP BY + HAVING Lett

Hva er forskjellen på WHERE og HAVING, og i hvilken rekkefølge utføres de?

Logisk utføringsrekkefølge: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT.

WHERE filtrerer rader før gruppering. Kan ikke referere til aggregatfunksjoner — de eksisterer ikke ennå.

HAVING filtrerer grupper etter at GROUP BY har samlet dem. Får referere til aggregatfunksjoner: HAVING SUM(salary) > 1 000 000.

Tommelfingerregel: hører predikatet til en enkeltrad, bruk WHERE; hører det til hele gruppa, bruk HAVING. Optimizeren kan flytte enkle ikke-aggregat-predikater fra HAVING til WHERE — men semantikken er definert i denne rekkefølgen.

Pensum: Kap. 3 — Gruppering

Kap. 3 — Subqueries Middels

Hva er forskjellen på IN, EXISTS og = ANY i en subquery? Når er NOT IN farlig?

x IN (subq) = TRUE hvis x er lik noen verdi i subqueryen. Tilsvarer x = ANY (subq).

EXISTS (subq) = TRUE hvis subqueryen returnerer minst én rad. Bryr seg ikke om kolonneverdier — typisk korrelerte subqueries der vi sjekker «finnes det en match?».

= ANY / = SOME = synonym for IN. = ALL = TRUE hvis x er lik alle verdier i subqueryen (inkludert tom subquery, der ALL er trivielt TRUE).

NOT IN er farlig med NULL: x NOT IN (a, NULL) blir alltid UNKNOWN → 0 rader. Bruk NOT EXISTS i stedet — det håndterer NULL korrekt og er ofte raskere.

Pensum: Kap. 3 — Subqueries

Kap. 3 — Views Vanskelig

Når er en view updatable, og hva gjør WITH CHECK OPTION?

En view er updatable når DBMS-en utvetydig kan oversette en INSERT/UPDATE/DELETE på viewet til en operasjon på underliggende tabeller. Typiske krav:

• Bygges på én tabell.
• Inneholder primærnøkkelen til den tabellen.
• Ingen aggregat, GROUP BY, DISTINCT, eller utregnede uttrykk i SELECT.
• Ingen subquery i WHERE som korrelerer med viewet selv.

Multi-tabell-views med JOIN er typisk read-only, eller krever INSTEAD OF-triggere.

WITH CHECK OPTION sørger for at en INSERT eller UPDATE på viewet ikke produserer en rad som ikke ville vært synlig gjennom viewet. F.eks. view HighEarners = SELECT * FROM Emp WHERE salary > 1 000 000; uten WITH CHECK OPTION kan jeg sette inn en rad med salary=500 000 (den havner i tabellen, men ikke i viewet); med WITH CHECK OPTION blir INSERT-en avvist.

Pensum: Kap. 3 — Views

Kap. 3 — Triggere Middels

Forklar forskjellen på row-level (FOR EACH ROW) og statement-level triggere. Hva er NEW og OLD?

Row-level (FOR EACH ROW): trigger-koden kjøres én gang for hver rad som påvirkes. Pseudovariablene NEW og OLD refererer til den enkelte raden.

OLD: gammel verdi (i UPDATE og DELETE).
NEW: ny verdi (i INSERT og UPDATE).
BEFORE-triggere kan endre NEW; AFTER-triggere kan ikke (raden er allerede skrevet).

Statement-level: kjøres én gang per setning, uansett antall rader berørt. NEW/OLD finnes ikke direkte; man bruker transition tables (NEW TABLE / OLD TABLE) som inneholder alle endrede rader.

Typisk valg: row-level for fine-grained logging og kaskaderegler; statement-level for å oppdatere et aggregert mellomresultat (f.eks. en sum-tabell), der man bare vil ha én oppdatering pr. statement.

Pensum: Kap. 3 — Triggere

Kap. 3 — GRANT/REVOKE CASCADE Middels

Hva betyr GRANT … WITH GRANT OPTION, og hvordan oppfører REVOKE … CASCADE seg?

WITH GRANT OPTION: mottakeren får ikke bare privilegiet, men også retten til å videregi det. Hvis Alice får SELECT på Emp WITH GRANT OPTION, kan hun GRANTe SELECT videre til Bob.

REVOKE … CASCADE: når et privilegium tilbakekalles, tilbakekalles også alle privilegier som er videregitt fra det. Hvis vi revoker SELECT fra Alice CASCADE, mister Bob det også (forutsatt at han ikke har SELECT fra en annen kilde).

REVOKE … RESTRICT: motsatt — REVOKE feiler hvis privilegiet er videregitt. Du må først rydde opp manuelt.

DBMS-en holder en grant-graf for å vite hvem som har gitt hva til hvem. CASCADE traverserer grafen; et privilegium beholdes hvis brukeren har det fra en alternativ kilde.

Pensum: Kap. 3 — Autorisasjon

Kap. 3 — Rekursive views Vanskelig

Hva er den generelle strukturen til en WITH RECURSIVE-spørring, og hvordan termineres rekursjonen?

WITH RECURSIVE r(...) AS (
  -- 1. base case: ikke-rekursiv start-spørring
  SELECT ... FROM ...
  UNION [ALL]
  -- 2. recursive step: refererer til r selv
  SELECT ... FROM r JOIN ...
)
SELECT ... FROM r;

DBMS-en gjør fixed-point iteration: start med base case, kjør recursive step gjentatte ganger, akkumuler nye rader i r. Stopp når en iterasjon ikke produserer flere nye rader.

UNION ALL beholder duplikater — vanlig hvis grafen er asyklisk. UNION de-dupliserer og fungerer som naturlig terminator i sykliske grafer (f.eks. forfedre i en familietre der en person ikke er sin egen forfar).

Klassisk brukstilfelle: transitive closure (alle forfedre, alle understående underavdelinger, korteste vei). Ofte mye penere enn pleie å gjøre flere selv-joiner manuelt.

Pensum: Kap. 3 — Rekursjon

Kap. 3 — Referanseintegritet Lett

Hva betyr ON DELETE CASCADE, ON DELETE SET NULL og ON DELETE RESTRICT på en fremmednøkkel?

Fremmednøkkelen B → A (B referer A) bestemmer hva som skjer i B når en referert rad i A slettes:

CASCADE: slett alle rader i B som peker på den slettede A-raden. Kan utløse en kaskade videre hvis B også er referert.

SET NULL: sett fremmednøkkelen i B til NULL. Krever at kolonnen er nullable.

RESTRICT (eller NO ACTION): avvis slettingen av A hvis det finnes refererende rader i B. Sikreste valget; brukes når man vil hindre dataforringelse.

Tilsvarende ON UPDATE-varianter finnes for primærnøkkel-endringer. RESTRICT sjekker umiddelbart, NO ACTION er deferrable til commit (i full SQL-standard, men ikke alle DBMS-er støtter dette).

Pensum: Kap. 3 — Integritet

Kap. 4 — Svake entiteter Middels

Hva er en svak entitet, hva er en identifying relationship, og hvordan oversettes den til relasjonsskjema?

En svak entitet har ikke en unik identifikator i seg selv. Den må kombineres med en eier-entitet via en identifying relationship for å bli identifiserbar.

Eksempel: Dependent (forsørget familiemedlem) har attributt name, men to ansatte kan ha hver sitt barn med samme navn. Sammen med Employee via «is dependent of» blir (SSN_emp, name) unik. Det partielle nøkkelattributtet (name) kalles discriminator.

Mapping til skjema: én relasjon med kombinert primærnøkkel: eierens primærnøkkel + svakens partielle nøkkel. Eierens nøkkel blir samtidig fremmednøkkel med ON DELETE CASCADE — sletter eieren, slettes også de avhengige.

I ER-diagram: dobbel rektangel for svak entitet, dobbel diamant for identifying relationship.

Pensum: Kap. 4 — ER-modellen

Kap. 4 — ISA Vanskelig

Tre vanlige måter å mappe en ISA-hierarki (specialization) til relasjoner. Hva er trade-off-ene?

1. Én tabell per type, inkl. superklasse: Person(id, navn) + Student(id, snitt) + Ansatt(id, lønn). Hver subklasse har FK til superklassen.
Fordeler: ren modell, støtter overlapping og partial. Ulemper: mange joiner ved spørringer som krysser hierarkiet.

2. Én tabell per subklasse (uten superklasse): Student(id, navn, snitt), Ansatt(id, navn, lønn). Krever disjoint+total: hver person er enten student eller ansatt, ikke begge.
Fordeler: ingen joiner. Ulemper: duplisert kolonne (navn); UNION ved spørringer på alle personer.

3. Én stor tabell med union types: Person(id, navn, type, snitt, lønn). Bruker NULL i felt som ikke gjelder.
Fordeler: ingen joiner. Ulemper: mange NULL, vanskelig å håndheve at student har snitt og ansatt har lønn.

Pensum bruker disjoint vs overlapping og total vs partial som constraints på spesialiseringa.

Pensum: Kap. 4 — ER-mapping

Kap. 4 — FD-lukning Middels

Hvordan beregner du attributt-lukningen X⁺ over et sett funksjonelle avhengigheter F, og hva bruker vi den til?

Algoritme:

1. Start med X⁺ = X.
2. Gå gjennom alle FD-er i F. Hvis venstre side Y ⊆ X⁺, legg høyre side Z til X⁺.
3. Repeter til X⁺ ikke vokser.

Brukes til:

Sjekke om X er supernøkkel: X⁺ = R (alle attributter) ⇔ X er supernøkkel.
Finne alle kandidatnøkler: sjekk hver minimal mengde X med X⁺ = R.
Sjekke en FD Y → Z: Z ⊆ Y⁺ ⇔ FD-en holder.
Bestemme normalform: for BCNF, sjekk at hver ikke-triviell FD har en supernøkkel som venstre side.

Pensum: Kap. 4 — FD og lukning

Kap. 4 — 3NF vs BCNF Vanskelig

Når er en relasjon på 3NF, når er den på BCNF, og hvorfor sikter vi noen ganger «kun» til 3NF?

BCNF: for hver ikke-triviell FD X → A, må X være en supernøkkel.

3NF: for hver ikke-triviell FD X → A, må én av disse holde:
(a) X er supernøkkel, eller
(b) A er en del av en kandidatnøkkel («prime attribute»).

3NF er altså strengere svakere enn BCNF — den tillater FD-er der venstresiden ikke er supernøkkel, så lenge høyresiden er prime.

Hvorfor 3NF noen ganger? Det er ikke alltid mulig å dekomponere til BCNF samtidig som vi bevarer alle FD-er. 3NF kan alltid oppnås tap-fritt OG dependency-bevarende; BCNF garanterer bare tap-fritt. Den klassiske eksempel-relasjonen er Address(by, postnummer, gate) der {by, gate} → postnummer og postnummer → by: kan dekomponeres til BCNF, men da kan ikke FD-en {by, gate} → postnummer håndheves uten å rejoine.

Pensum: Kap. 4 — Normalformer

Kap. 4 — Kandidatnøkler Middels

R(A, B, C, D) med F = {A → B, B → C, CD → A}. Hvor mange kandidatnøkler har R?

  • A 1: {AD}
  • B 2: {AD}, {BD}
  • C 3: {AD}, {BD}, {CD}
  • D 4: {AD}, {BD}, {CD}, {ABCD}

Riktig: C — 3 kandidatnøkler.

D er ikke i høyresiden av noen FD, så D må være med i hver kandidatnøkkel.

Sjekk hver kandidat:

{A,D}⁺: A → B → C, så lukning = ABCD ✓
{B,D}⁺: B → C, og {C,D} → A, så lukning = ABCD ✓
{C,D}⁺: CD → A → B, så lukning = ABCD ✓
• Bare D: lukning = D ✗

Alle tre er minimale (fjerner du D, eller den andre attributten, blir lukningen ufullstendig). Derfor 3 kandidatnøkler.

D (4) teller med en ikke-minimal supernøkkel.

Pensum: Kap. 4 — Kandidatnøkler

Kap. 4 — Lossless decomposition Vanskelig

Hva er testen for at en dekomponering av R til R₁ og R₂ er lossless join?

Dekomponeringen er lossless hvis fellesattributtene R₁ ∩ R₂ er supernøkkel for minst én av delrelasjonene.

Formelt: (R₁ ∩ R₂) → R₁ ELLER (R₁ ∩ R₂) → R₂ må holde i F⁺.

Hvorfor: hvis fellesattributtene unikt bestemmer den ene siden, finnes det bare én måte å koble en R₁-tuppel tilbake til R₂-tupler — vi taper ingen informasjon ved natural join. Hvis ingen av sidene er supernøkkel for fellesnøkkelen, kan vi få spurious tupler ved rejoin.

Eksempel: R(SSN, navn, by) med {SSN → navn, SSN → by}. Splitt til R₁(SSN, navn) og R₂(SSN, by) — fellesnøkkel SSN er supernøkkel for begge → lossless. Splitt i stedet til R₁(SSN, navn) og R₂(navn, by) — felles er navn, som ikke er supernøkkel → potensielt tap (hvis to personer har samme navn).

For dekomponering i mer enn to relasjoner: bruk chase-algoritmen.

Pensum: Kap. 4 — Lossless decomposition

Kap. 6A — RID Lett

Hva er en RID, og hvorfor består den av en blokk-id pluss en slot — ikke en absolutt byte-offset?

RID = (BlockId, slot), en peker fra en indeks ned til en konkret rad.

Hadde RID-en vært en absolutt byte-offset, ville hver kompaktering av blokken (etter sletting f.eks.) krevd at alle indekser ute i systemet ble oppdatert. Det er praktisk umulig.

Ved å bruke en logisk slot-indeks som indirekteringspunkt, kan radene flyttes inni blokken uten at indekspekerne blir ugyldige — bare blokkens slot directory må oppdateres.

Pensum: Kap. 6A — Block-format

Kap. 6A — Buffer pool Middels

Hvorfor har DBMS-er egen buffer pool fremfor å la OS-et håndtere caching?

DBMS-en kjenner semantikken og kan derfor velge bedre policy enn et generisk OS:

Pinning: hold en blokk i bufferet så lenge en B+-traversering eller transaksjon trenger den.
Smart prefetching: ved table scan kan flere blokker leses inn i forkant.
WAL-håndhevelse: sørg for at logg-blokker skrives før commit (force-log-at-commit).
Bedre replacement enn LRU ved store sekvensielle scans (LRU forurenses ellers).

OS-et ser bare anonyme sidefeil og kan ikke skille en B+-rotnode fra en sjelden brukt arkivblokk.

Pensum: Kap. 6A — Buffer pool

Kap. 6A — Statisk hashing Middels

Hvilken hash-funksjon bruker pensum for statisk hashing, hva er overflow chains, og hvorfor er det et problem?

Pensums kanoniske funksjon: h(K) = K mod N, der N er antallet bøtter (faste, satt på forhånd).

Når en bøtte fylles opp, lenkes en overflow-blokk bak — fortsetter videre til en kjede hvis overflow-blokken også fylles.

Problemet: et oppslag «skulle» være én blokk-aksess, men kan ende opp som en lineær traversering av kjeden — verste fall blir like dårlig som heap-scan.

Statisk hashing har derfor en fastlåst kapasitet: vokser dataene utover det N er dimensjonert for, kollapser ytelsen. Løsningen er extendible hashing som lar antall bøtter doble seg dynamisk.

Pensum: Kap. 6A — Statisk hashing

Kap. 6A — Extendible hashing Vanskelig

I extendible hashing — hva er forskjellen på global depth og local depth, og når må katalogen dobles?

Global depth (d): antall bits av hash-verdien som directoryen ser på. Directoryen har 2^d innslag.

Local depth (d_b): antall bits som faktisk skiller bøttene fysisk — d_b ≤ d for hver bøtte.

Når en bøtte må splittes:

Hvis d_b < d: bøtta kan splittes uten å berøre directoryen. To eksisterende directory-innslag som tidligere pekte på samme bøtte, peker nå på hver sin nye bøtte. Local depth øker med 1.
Hvis d_b = d: directoryen må dobles først — alle innslag dupliseres, deretter splittes bøtta. Global depth øker med 1.

Slik vokser og krymper directoryen dynamisk uten å rehashe alle nøkler — bare den splittete bøtta og dens 2^(d - d_b) directory-pekere oppdateres.

Pensum: Kap. 6A — Extendible hashing

Kap. 6A — Heap-fil-størrelse Middels

Tabell Order har 500 000 rader, 64 byte per rad, blokkstørrelse 4 KiB. Hvor mange blokker bruker en ren heapfil (uten fyllgrad)?

  • A 7813 blokker
  • B 11 719 blokker
  • C 15 625 blokker
  • D 500 000 blokker

Riktig: A — 7813 blokker.

Heapfilen pakker fullt uten fyllgrad-marg.

rec_per_blokk = ⌊4096 / 64⌋ = 64
antall_blokker = ⌈500 000 / 64⌉ = 7813

B (11 719) bruker fyllgrad 2/3: 64 · 2/3 ≈ 42 rec/blokk. Det gjelder for B+-tre-blader, ikke for heap.
C (15 625) deler 500 000 / 32 — som om hver blokk bare hadde halv kapasitet.
D (500 000) antar én rad per blokk — glemmer pakking.

Pensum: Kap. 6A — Heap file

Kap. 6A — LSM-tre Middels

Hva er hovedidéen i et LSM-tre (log-structured merge tree), og hvilket I/O-mønster optimaliserer det for?

LSM-tre er optimalisert for tunge skrivelaster. Hovedidé: skriv aldri i tilfeldig rekkefølge — saml opp endringer i RAM, og skriv ut sekvensielt til disk.

Memtable: en sortert struktur (ofte rød-svart-tre eller skip-list) i RAM. Alle writes går hit først.
SSTable (sorted string table): når memtable fylles, skrives den ut som én sortert, immutable disk-fil. Sekvensiell skriving — mye raskere enn random writes på en HDD.
Compaction: i bakgrunnen merges små SSTables til større, sorterte SSTables. Sletter overskrevne rader.

Trade-off: writes er svært raske (write amplification lav), men reads kan måtte sjekke flere SSTables på flere nivåer (read amplification). B+-trær er motsatt: raske reads, dyrere random writes.

Brukes i Cassandra, RocksDB, LevelDB, HBase. Ikke MySQL/PostgreSQL — de bruker B+-trær.

Pensum: Kap. 6A — LSM

Kap. 6B — B+-tre fanout Lett

Hva er fanout i et B+-tre, og hvorfor er det så viktig at den er stor?

Fanout = maks antall barn per intern node = ⌊B · 2/3 / S⌋ der B er blokkstørrelse og S er størrelsen på (key, BlockId)-paret.

Høy fanout gir lavt tre. Med 4 KiB-blokker, 16 byte per indekspost og fyllgrad 2/3 får vi fanout = 170. Et 3-nivåers tre dekker da opp til 170³ ≈ 4,9 millioner blader.

Hvert nivå koster én blokk-aksess ved likhetsoppslag. Stor fanout = få nivåer = få diskaksesser = rask. B+-trær er typisk 3-4 nivåer dype selv med milliarder av rader.

Sammenligning: en binær søketre over samme data ville vært ~log₂(milliarder) = 30 nivåer. 30 diskaksesser per oppslag = uakseptabelt.

Pensum: Kap. 6B — B+-trær

Kap. 6B — Clustered vs unclustered Vanskelig

Skill mellom clustered og unclustered indeks. Hvorfor kan en eksisterende indeks gjøre en spørring tregere enn en full scan?

Clustered: radene er fysisk sortert i nøkkelorden. Bladene er tabellen. Ett oppslag → ett blad → ferdig. Range-scans = sammenhengende blokker.

Unclustered: radene er sortert annerledes (typisk innsettingsrekkefølge i en heap). Bladene inneholder (key, RID), ikke selve raden. Hvert oppslag følger RID-en til en (potensielt tilfeldig) heap-blokk.

Hvorfor kan unclustered være verre enn full scan? Hvis spørringen treffer mange rader, blir det én tilfeldig heap-aksess per treff. 50 000 treff på en 1961-blokks heap kan bety 50 000 blokker (med duplikater). Full scan = 1961 blokker. Optimizeren ser dette via statistikk og velger full scan.

Tommelfingerregel: unclustered indeks lønner seg bare ved selektive predikater (få treff). Clustered er bra også ved range-spørringer.

Pensum: Kap. 6B — Clustered vs unclustered

Kap. 6B — B+-tre-høyde Middels

En unclustered B+-indeks har 800 000 (key, RID)-poster. Indeksposter er 16 byte, blokk er 4 KiB, fyllgrad 2/3. Hvor mange nivåer (inkl. rota og bladene)?

  • A 2 nivåer
  • B 3 nivåer
  • C 4 nivåer
  • D 5 nivåer

Riktig: B — 3 nivåer.

Poster per blokk: ⌊4096 · 2/3 / 16⌋ = ⌊170.67⌋ = 170 (fanout og bladkapasitet).

• Bladnivå: ⌈800 000 / 170⌉ = 4706 blokker.
• Nivå 1: ⌈4706 / 170⌉ = 28 blokker.
• Rotnivå: ⌈28 / 170⌉ = 1 blokk.

Tre nivåer totalt. Likhetsoppslag = 3 indeks-aksesser + 1 heap-aksess for selve raden = 4 blokker.

A er for få: 4706 blader passer ikke under én rot (maks 170 barn).
C/D er for mange: fanouten på 170 er stor nok til at 4706 ruller seg ned til 1 rot på 2 hopp.

Pensum: Kap. 6B — Tre-høyde

Kap. 6B — B+-insert + split Vanskelig

Beskriv hva som skjer når en innsetting i et B+-tre fører til at en bladblokk er full og må splittes.

Anta blad har plass til 4 nøkler og fylles til 5 etter innsetting:

1. Splitt bladet: de 5 nøklene fordeles på to blader. Vanlig pensumkonvensjon: ⌈5/2⌉ = 3 i venstre blad, 2 i høyre (eller motsatt — mindre viktig hva som velges, så lenge det er konsistent).

2. Oppdater bladlenker: det nye høyre-bladet lenkes inn i den lenkede listen (venstre.next = nytt blad; nytt.next = gamle høyre).

3. Send opp én nøkkel til foreldren: minste nøkkelen i det høyre nye bladet kopieres opp og settes inn som separator i forelderen — sammen med peker til det nye bladet.

4. Hvis forelderen overskrider sin kapasitet, splittes også den — men der flyttes midt-nøkkelen oppover (ikke kopieres). Splitten kan kaskadere helt opp til rota; hvis rota må splittes, vokser treet med ett nivå.

Konsekvens: én innsetting kan i verste fall trigge log(n) blokk-skrivinger.

Pensum: Kap. 6B — Innsetting

Kap. 6B — Composite key Middels

Hvilke spørringer kan dra nytte av en sammensatt indeks (land, by, postnr), og hvilke kan ikke?

Sammensatte indekser er sortert leksikografisk — først på land, så by, så postnr. Indeksen kan brukes når spørringen filtrerer på et venstre prefiks:

WHERE land = 'NO' — kun første kolonne, OK.
WHERE land = 'NO' AND by = 'Trondheim' — to første, OK.
WHERE land = 'NO' AND by = 'Trondheim' AND postnr = 7012 — alle tre, optimalt.
✓ Også range på siste leddet: WHERE land='NO' AND by='Trondheim' AND postnr BETWEEN 7000 AND 7099.

WHERE by = 'Trondheim' alene — hopper over land, indeksen er ubrukelig.
WHERE postnr = 7012 — hopper over begge, ubrukelig.
WHERE land = 'NO' AND postnr = 7012 — bruker land, men kan ikke sno seg til postnr uten å gå gjennom hele by-rangen for NO.

Tommelfingerregel: kolonnerekkefølgen i indeksen avgjør hvilke spørringer den støtter.

Pensum: Kap. 6B — Composite key

Kap. 7 — Selektivitet Middels

Hvordan estimeres selektivitetsfaktoren (sf) for likhets- og range-predikater, og hvordan kombineres flere predikater?

Selektivitetsfaktoren = andelen rader som overlever predikatet (mellom 0 og 1).

Likhet (col = c): antar uniform fordeling, sf = 1 / V(col, R) der V er antallet distinkte verdier.

Range (col > c): sf = (high − c) / (high − low).

BETWEEN c1 AND c2: sf = (c2 − c1) / (high − low).

Kombinasjon:

P1 AND P2 (uavhengige): sf = sf₁ · sf₂.
P1 OR P2: sf = sf₁ + sf₂ − sf₁·sf₂ (inklusjon-eksklusjon).
NOT P: sf = 1 − sf.

Forventet antall treff: |R| · sf. Bedre estimater fås med histogrammer — DBMS-en kan da slippe uniform-antagelsen.

Pensum: Kap. 7 — Statistikk

Kap. 7 — Kostnadsbasert optimizer Middels

Hva er en cost-based optimizer, og hvilken statistikk leser den fra SQL-katalogen?

En cost-based optimizer enumererer kandidatplaner og regner en forventet kostnad (typisk antall blokk-aksesser) for hver. Velger den billigste.

Statistikk i SQL-katalogen:

|R|: antall rader i tabellen.
b_R: antall blokker tabellen bruker.
V(A, R): antall distinkte verdier av attributt A.
min(A), max(A): lavest/høyest forekommende verdi (lowkey/highkey).
h_R: høyde av et eventuelt B+-tre på R.
• Histogrammer: bredde- eller høydebalanserte buckets med radtellere — gir bedre selektivitetsestimater enn antagelsen om uniform fordeling.

Statistikken er ikke alltid fersk: ANALYZE TABLE i MySQL eller VACUUM ANALYZE i PostgreSQL oppdaterer den. Foreldet statistikk er en klassisk grunn til at optimizeren plutselig velger en dårlig plan.

Pensum: Kap. 7 — SQL-katalog

Kap. 7 — External merge sort Vanskelig

b = 2000 blokker, n_B = 11 buffere. Total I/O for external merge sort?

  • A 12 000 blokker
  • B 16 000 blokker
  • C 20 000 blokker
  • D 8 000 blokker

Riktig: A — 12 000 blokker.

Initial-passet leser inn n_B blokker om gangen, sorterer i RAM, skriver ut som en run.
n_R = ⌈b / n_B⌉ = ⌈2000 / 11⌉ = 182 initial runs.

Merge-faktor: d_M = n_B − 1 = 10 (én buffer brukes til output).
Antall merge-passes: ⌈log_d_M(n_R)⌉ = ⌈log_10(182)⌉ = ⌈2.26⌉ = 3.

Total I/O: 2b · (1 + passes) = 2·2000·(1+2) = 12 000 blokker.

Merk: «1 + passes» — initial-passet teller som ett pass; deretter kommer 2 ekte merge-passes (etter «log_10(182) ≈ 2.26» rundes opp til 3, men det er fordi vi tester med log_d_M(n_R/n_B) i noen kompendier — sjekk hvilken konvensjon kurset bruker. Pensum-formelen ⌈log_d_M(n_R)⌉ over n_R = 182 gir 3 passes; «1 + 2» merge-passes er en alternativ telling). Tommelregel: les fasiten i pensum-eksemplet og bruk samme telling konsekvent.

I dette tilfellet med 2 merge-passes: total = 2·2000·(1+2) = 12 000.

Pensum: Kap. 7 — External merge sort

Kap. 7 — Aksessveier Middels

Nevn de fire aksessveiene en optimizer kan velge mellom, og når hver av dem vinner.

1. Filscan (full table scan): les alle blokker. Vinner hvis predikatet matcher mange rader, eller om det ikke finnes en passende indeks.

2. Indeks-lookup (likhet på indekset attributt): traverser ned indeksen, finn én eller få rader. Vinner ved selektive likhetspredikater og clustered eller få-treff unclustered.

3. Range-scan på indeks: traverser ned til startnøkkel, gå sekvensielt gjennom bladnoder til stoppnøkkel. Vinner ved range-predikater på en clustered indeks.

4. Indeksscan (full indeks-scan): les alle blader uten å gå via heap. Vinner når spørringen kan besvares fra kun indeksen (covering index) — f.eks. SELECT col FROM R der col er indeksert, eller COUNT(*) som bare trenger antall.

Optimizeren regner kostnad for hver lovlige aksessvei og velger den billigste basert på selektivitet og statistikk.

Pensum: Kap. 7 — Aksessveier

Kap. 7 — Block nested loop join Vanskelig

Department har 8 blokker, Employee har 1200 blokker, n_B = 12 buffere. Hva er kostnaden av en block nested loop join (J1) med Department som ytre tabell?

  • A 1208 blokker
  • B 9608 blokker
  • C 2408 blokker
  • D 9600 blokker

Riktig: A — 1208 blokker.

Formel: b_ytre + ⌈b_ytre / (n_B − 2)⌉ · b_indre (én buffer til indre, én til output, resten til ytre).

= 8 + ⌈8 / 10⌉ · 1200 = 8 + 1 · 1200 = 1208

Hele Department får plass i én «chunk» på 10 buffere, så Employee leses bare én gang.

B (9608) tar Employee som ytre: 1200 + ⌈1200/10⌉ · 8 = 1200 + 120·8 = 2160 — eller med n_B=2 gir 1200 + 1200·8 = 10 800. Disse tallene matcher ikke nøyaktig — en typisk distraktor med feil ytre-tabell.
C (2408) regner med 200 (n_B/2) i stedet for n_B−2.
D (9600) = 1200 · 8: glemmer at ytre Department leses bare én gang.

Lærdom: velg den minste tabellen som ytre for å minimere antall passes over indre tabell.

Pensum: Kap. 7 — J1 block nested loop

Kap. 7 — J2 index nested loop Middels

Når er index nested loop join (J2) en god idé, og hva er den typiske kostnadsformelen?

J2 vinner når det finnes en indeks på indre tabells join-attributt, og ytre tabell er liten eller predikat-filtrert.

Algoritme: for hver rad i ytre R, slå opp matchende rader i indre S via indeksen.

Kostnad (clustered indeks på S):
kost = b_R + |R| · (h_S + 1)
der h_S = høyden på B+-treet på S og «+ 1» er heap-aksessen (eventuelt mer enn 1 hvis flere rader matcher per oppslag).

Kostnad (unclustered indeks på S):
kost = b_R + |R| · (h_S + matcher_per_R)
der hver match koster én ekstra heap-blokk (med duplikater).

Sammenligning med J1: J2 sparer hele scan av S, men betaler én indekstraversering per ytre rad. Vinner når |R| er liten relativt til b_S / (h_S + 1).

Pensum: Kap. 7 — J2 index nested loop

Kap. 7 — J3 sort-merge Middels

Beskriv sort-merge join (J3): når lønner den seg, og hva er totalformelen for I/O?

Algoritme:

1. Sorter R på join-attributtet (hvis ikke allerede sortert).
2. Sorter S på join-attributtet.
3. Merge: to pekere som beveger seg parallelt gjennom de sorterte tabellene; emittér par når nøklene matcher.

Kostnad: sort(R) + sort(S) + b_R + b_S.

Sort-koster: 2·b · (1 + ⌈log_d_M(n_R)⌉) for hver tabell (samme som external merge sort).

Sort-merge vinner når:
• Begge tabeller er store og ingen indeks finnes.
• Tabellene allerede er sortert på join-attributtet (fra et clustered indeks eller en tidligere ORDER BY) — da blir kosten bare b_R + b_S, dramatisk billig.
• Output-en skal sorteres på join-attributtet uansett — sortering blir gratis.

Hvis bare én av tabellene er sortert, sorter den andre.

Pensum: Kap. 7 — J3 sort-merge

Kap. 7 — J4 hash join Vanskelig

Hvordan fungerer partition hash join (J4), og hva er typisk total I/O-kostnad?

To faser:

Build / partition: bruk en hash-funksjon h₁ på join-attributtet, partisjoner R og S i samme antall partisjoner. Hver partisjon havner i én bucket-fil — radene fra R og S med samme hash-verdi havner i samme bucket-par.

Probe: for hvert par av partisjoner (R_i, S_i): bygg en hash-tabell på den minste i RAM, probe med den større. Bruk en annen hash-funksjon h₂ for å unngå kollisjoner som allerede ble brukt til partisjonering.

Kostnad: 3 · (b_R + b_S) — les begge én gang (partisjonere), skriv begge én gang, les begge én gang (probe). Antar at hver partisjon får plass i RAM.

Hvis en partisjon er for stor: rekursiv re-partisjonering (recursive hash join) eller hybrid hash join.

Vinner over sort-merge når det ikke finnes nyttig sortering, og over nested loop når begge tabeller er store.

Forutsetter ekvi-join (likhetsbetingelse). Theta-join som R.a < S.b kan ikke gjøres med hash.

Pensum: Kap. 7 — J4 hash join

Kap. 7 — Plan-valg Vanskelig

Hvilken join-algoritme vinner i hvilken situasjon? (Oppsummer J1–J4.)

J1 — Block nested loop: liten ytre tabell, indre uten indeks. Robust «fallback» når ingenting annet passer.

J2 — Index nested loop: liten ytre tabell, og indeks finnes på indre tabells join-attributt. Klart raskest når ytre er svært selektiv (få rader).

J3 — Sort-merge: begge tabeller er store og ingen indeks. Spesielt bra når tabellene allerede er sortert (f.eks. fra en clustered indeks eller en tidligere ORDER BY).

J4 — Hash: begge tabeller store, ingen sortering, ingen indeks. Vinner over sort-merge ved store datamengder; forutsetter ekvi-join.

Optimizeren regner kostnaden for alle de aktuelle algoritmene (gitt aksessveier, indekser og sortering) og velger den billigste — det er hele jobben i kapittel 7.

Pensum: Kap. 7 — Join-algoritmer

Kap. 8 — Konfliktserialiserbarhet Middels

Hva er en konflikt mellom to operasjoner i et schedule, og hvordan tester du om et schedule er konfliktserialiserbart?

To operasjoner er i konflikt hvis:
(a) de tilhører forskjellige transaksjoner,
(b) de aksesserer samme dataobjekt, og
(c) minst én av dem er en write.

Konflikttypene: r-w, w-r, w-w. To reads (r-r) er aldri i konflikt.

Test for konfliktserialiserbarhet:

1. Bygg presedensgraf: én node per transaksjon. Tegn kant T_i → T_j hvis det finnes en konflikt der T_i sin operasjon kommer før T_j sin.
2. Schedule-en er konfliktserialiserbar ⇔ presedensgrafen er asyklisk.
3. Hvis asyklisk: enhver topologisk sortering av grafen gir et serielt schedule som er konfliktekvivalent.

Konfliktserialiserbart ⊂ view-serialiserbart ⊂ serialiserbart. I praksis bruker DBMS-er konfliktserialiserbarhet, fordi det er polynomisk avgjørbart (mens view-serialiserbarhet er NP-komplett).

Pensum: Kap. 8 — Serialiserbarhet

Kap. 8 — Recoverability Vanskelig

Forklar hierarkiet Strict ⊂ ACA ⊂ Recoverable ⊂ All.

Recoverable: en transaksjon T_j som leser fra T_i må commit'e etter T_i. Hvis ikke kan en abort av T_i etter T_j sin commit ikke rulles tilbake — vi har committed et resultat basert på data som ble fjernet.

ACA (Avoids Cascading Aborts): stricter — T_j kan ikke i det hele tatt lese en write fra T_i før T_i har commit'et. Dermed kan en abort aldri kaskadere til andre transaksjoner.

Strict: enda strictere — T_j kan verken lese eller overskrive en write fra T_i før T_i har commit'et. Dette gir også enkel before-image recovery (UNDO trenger bare den siste verdien).

Inklusjonen er ekte: alle Strict er ACA, alle ACA er Recoverable, alle Recoverable er gyldige schedules — men ikke omvendt.

Strict 2PL (rigorous 2PL) gir Strict schedules. Basic 2PL gir bare Recoverable.

Pensum: Kap. 8 — Recoverability

Kap. 8 — 2PL-varianter Middels

Forklar de fire 2PL-variantene: basic, conservative, strict, rigorous. Hvilke garanterer hva?

Basic 2PL: alle låser tas før den første låsen slippes (én vekstfase, så én skrumpefase). Garanterer konfliktserialiserbarhet, men ikke recoverable schedules. Kan ha kaskaderende abort.

Conservative 2PL: alle låser tas før transaksjonen starter. Hvis en lås er opptatt, blokkerer transaksjonen helt før den begynner. Deadlock-fri, men krever at hele lock-settet er kjent på forhånd.

Strict 2PL: alle X-låser beholdes til commit/abort. Garanterer Strict schedules + serialiserbarhet. Vanligst i praksis.

Rigorous 2PL: alle låser (S og X) beholdes til commit/abort. Slipper at conflicts kan oppstå sent. Implementeringsmessig enklere enn strict; samme egenskaper.

Alle fire kan deadlocke (unntatt conservative). Strict og rigorous er de eneste som garanterer både serialiserbarhet OG strict schedules.

Pensum: Kap. 8 — 2PL

Kap. 8 — Lock compatibility Lett

Hvordan ser lock-compatibility-matrisen ut for S- og X-låser? Og hva legger intent-låser (IS, IX, SIX) til?

Basic S/X-matrise:

          S      X
   S      ✓      ✗
   X      ✗      ✗

To S kan eksistere samtidig (mange lesere). En X eksluderer alt.

Intent-låser brukes ved hierarkisk låsing (database → tabell → blokk → rad):

IS (Intent Shared): «jeg planlegger å ta en S-lås på en under-node».
IX (Intent Exclusive): «jeg planlegger en X-lås nedover».
SIX (Shared + Intent Exclusive): «leser hele tabellen, men vil oppdatere noen rader».

Utvidet matrise:

         IS    IX    S    SIX    X
   IS    ✓    ✓    ✓    ✓     ✗
   IX    ✓    ✓    ✗    ✗     ✗
   S     ✓    ✗    ✓    ✗     ✗
   SIX   ✓    ✗    ✗    ✗     ✗
   X     ✗    ✗    ✗    ✗     ✗

Før T tar en S-lås på en rad, må den først ta IS på tabellen og databasen. Slik vet andre transaksjoner som vil ta X på hele tabellen at de må vente.

Pensum: Kap. 8 — Låser

Kap. 8 — Deadlock Vanskelig

Hva er en wait-for graph, og forklar de to vanligste forebyggingsstrategiene wait-die og wound-wait.

Wait-for graph: én node per aktiv transaksjon; kant T_i → T_j hvis T_i venter på en lås holdt av T_j. Sykel = deadlock. DBMS-en sjekker periodisk; ved sykel velges en victim (typisk yngst transaksjon, eller den som har gjort minst arbeid) og aborts.

Forebygging i stedet for deteksjon: gi hver transaksjon et tidsstempel ved start. Eldre tidsstempel = høyere prioritet.

Wait-die (preemption-fri): hvis T_i ber om lås holdt av T_j:
• T_i eldre: T_i venter (eldre prioriteres).
• T_i yngre: T_i aborts og restartes med samme tidsstempel.

Wound-wait (preemptiv): hvis T_i ber om lås holdt av T_j:
• T_i eldre: T_j aborts (eldre «sårer» yngre).
• T_i yngre: T_i venter.

Begge garanterer deadlock-frihet fordi venting kun skjer i én retning av tidsstempel-orden. Wound-wait færre restarts (eldre fullfører raskere); wait-die enklere implementasjon.

Pensum: Kap. 8 — Deadlock

Kap. 8 — MVCC Vanskelig

Hvordan fungerer snapshot isolation (MVCC), og hva er write skew?

MVCC (multi-version concurrency control): hver write skaper en ny versjon av raden, merket med tidsstempel for transaksjonen som skrev den. Reads ser den versjonen som var nyest ved transaksjonens start (snapshot).

Konsekvens: readers blokkerer aldri writers, og writers blokkerer aldri readers. Bare write-write-konflikter krever koordinering — typisk via en sjekk ved commit (first-committer-wins).

Snapshot isolation: isolasjonsnivå basert på MVCC. Forhindrer dirty read, non-repeatable read, lost update — men ikke alle anomalier.

Write skew (klassisk anomali under SI): to transaksjoner leser overlappende data og skriver til forskjellige objekter, basert på et felles invariant. Eksempel: «minst én lege må være på vakt». T1 og T2 ser begge to leger på vakt; begge bestemmer at det er trygt å ta seg fri; begge oppdaterer sin egen status. Ingen konflikt på samme rad → begge committer → ingen lege på vakt. Invariant brutt.

Løsning: serializable snapshot isolation (SSI) — sjekker også r-w-konflikter via en SI-utvidet wait-for graph. PostgreSQL SERIALIZABLE.

Pensum: Kap. 8 — MVCC

Kap. 8 — Anomalier Middels

Forklar dirty read, non-repeatable read, lost update og phantom read. Hvilket isolasjonsnivå hindrer hver?

Dirty read: T_j leser en uncommitted write fra T_i. Hvis T_i abortes, så T_j leste data som aldri eksisterte. Hindret av READ COMMITTED og oppover.

Non-repeatable read: T_i leser X, T_j endrer X og committer, T_i leser X igjen og får annen verdi. Hindret av REPEATABLE READ og oppover (typisk via S-låser holdt til commit).

Lost update: T_i leser X = 100, T_j leser X = 100, begge øker med 1, begge skriver tilbake X = 101 — én oppdatering tapt. Hindret av REPEATABLE READ i mange DBMS-er, eller via SELECT … FOR UPDATE.

Phantom read: T_i kjører SELECT … WHERE p og får 5 rader; T_j inserter en ny rad som matcher p og committer; T_i kjører samme SELECT og får 6 rader. Hindret av SERIALIZABLE (typisk via predicate locking eller index-range locking).

Anomalier som ikke hindres av SERIALIZABLE i SI: write skew (se MVCC-kortet) — kreves SSI eller eksplisitt SELECT FOR UPDATE.

Pensum: Kap. 8 — Isolasjonsnivåer

Kap. 8 — Force/Steal-matrise Vanskelig

Forklar matrisen Force vs No-force × Steal vs No-steal. Hvilken kombinasjon bruker ARIES, og hvilken må kompenseres med UNDO/REDO?

Matrisen handler om når dirty pages skrives til disk.

Force: alle endrede sider være på disk før commit. Ingen REDO trengs (det er allerede skrevet) — men commit blir tregt.
No-force: sider kan forbli i bufferet ved commit. Krever REDO ved krasj — vi må kunne reapply committed transaksjoner som ikke nådde disk.

Steal: en uncommitted transaksjons sider kan kastes ut av bufferet og skrives til disk. Krever UNDO ved krasj — vi må kunne reverse skrivinger fra ikke-committed transaksjoner.
No-steal: en sides side beholdes pinnet til commit. Trenger ikke UNDO.

             Force         No-force
Steal        UNDO          UNDO + REDO  ← ARIES
No-steal     ingen recovery  REDO

ARIES = Steal + No-force: maksimal fleksibilitet for buffer manageren (kan kaste ut hva som helst, kan utsette skriving til etter commit), men krever full UNDO+REDO via WAL.

WAL-regelen sikrer at logg-postene som beskriver en endring er på disk før selve siden — det er det som gjør UNDO mulig selv ved en steal-eviction.

Pensum: Kap. 8 — Recovery

Kap. 8 — ARIES tre faser Vanskelig

Beskriv de tre fasene i ARIES recovery: Analysis, Redo, Undo.

1. Analysis: les loggen fra siste checkpoint til slutten. Bygg opp:
Transaction Table (TT): alle transaksjoner som var aktive ved krasjet (ingen commit/abort-post sett).
Dirty Page Table (DPT): sider som var skitne (modifisert i bufferet, kanskje ikke skrevet til disk).
Etter analyse: vi vet hvem som var winners (committed) og losers (aktive ved krasj).

2. Redo: start fra min(recLSN) i DPT. For hver loggpost fremover:
• Hvis side i DPT, recLSN ≤ LSN, og PageLSN < LSN — utfør operasjonen på siden, sett PageLSN = LSN.
• Ellers: hopp over (allerede på disk, eller ikke i DPT).
Resultat: alle committed endringer er nå på disk.

3. Undo: start fra slutten av loggen, gå bakover. For hver loser-transaksjon: utfør UNDO av hver av deres write-poster. Skriv en CLR (compensation log record) for hver UNDO — sikrer at en ny krasj midt i recovery kan fortsette. Ferdig når alle losers er UNDO-et.

Merk rekkefølgen: REDO først (selv losers), så UNDO. Slik blir disk konsistent med loggen før vi reverserer.

Pensum: Kap. 8 — ARIES

Kap. 8 — REDO-test Vanskelig

Etter analyse-fasen står P5 i DPT med recLSN = 80. På disk har P5 PageLSN = 70. En loggpost har LSN = 90 og endrer P5. Skal denne posten REDO-es?

  • A Ja — alle krav er oppfylt.
  • B Nei — siden står ikke i DPT.
  • C Nei — recLSN er for høy.
  • D Nei — PageLSN er for høy.

Riktig: A — REDO-es.

ARIES REDO-test krever alle tre kriterier:

1. Side i DPT — ✓ (P5 er der).
2. recLSN ≤ LSN: 80 ≤ 90 — ✓.
3. PageLSN < LSN: 70 < 90 — ✓.

Alle tre kriterier matcher → utfør REDO, og oppdater PageLSN til 90.

Tolkning: P5 ble første gang skitnet av en loggpost ≥ recLSN (80). Endringen i LSN 90 er ikke ennå reflektert på disk (PageLSN = 70). Den må derfor reapplies.

B sjekker første kriterium feil. C: recLSN = 80, LSN = 90 → 80 ≤ 90 OK. D: PageLSN = 70 < LSN = 90, så test 3 bestås — vi redo.

Pensum: Kap. 8 — REDO-fase

Kap. 8 — WAL og CLR Middels

Hva er WAL-regelen, og hvorfor skriver ARIES en CLR (compensation log record) for hver UNDO?

WAL (write-ahead logging): før en endret side skrives til disk, må alle loggposter med LSN ≤ PageLSN være på disk. Konkret: før P kan flushes, må log[i] for alle i ≤ P.PageLSN være persistente.

Dette gir: hvis vi finner en endring på disk uten en korresponderende loggpost, er det en feil. Hvis vi ikke finner endringen på disk men har loggposten, kan vi REDO-e.

Ved commit: alle T_i sine loggposter — inkludert «commit T_i» — må være på disk. Force-log-at-commit. Selve datasidene kan ligge igjen i bufferet (no-force).

CLR (compensation log record): ved UNDO i recovery skriver ARIES en CLR for hver inverterte handling. CLR har en UndoNxtLSN-peker til neste loggpost som skal UNDO-es.

Hvorfor: hvis det krasjer midt i recovery, kan neste recovery se hvor langt forrige UNDO var kommet (via CLR-pekerne) og fortsette derfra — uten å undo'e samme operasjon to ganger. CLR er redo-only, aldri undo-et.

Pensum: Kap. 8 — WAL + CLR

Kap. 8 — LSN-feltene Vanskelig

Forklar de fire LSN-feltene LSN, PrevLSN, PageLSN og recLSN. Hvor lever hver?

LSN (Log Sequence Number): unikt nummer som hver loggpost får ved skriving. Monotont voksende. Lever i loggposten.

PrevLSN: peker fra en loggpost til forrige loggpost for samme transaksjon. Tillater UNDO å traversere transaksjonens skrivinger bakover uten å skanne hele loggen. Lever i loggposten.

PageLSN: LSN-en til den siste endringen som er reflektert på en disk-side. Lagres i sidens header. Brukes i REDO-testen: hvis PageLSN ≥ LSN av loggposten, er endringen allerede på disk — ikke gjør den igjen.

recLSN: i Dirty Page Table. Den første loggposten som skitnet siden etter at den sist ble skrevet til disk. recLSN er nedre grense for REDO-fasen — hvis LSN < recLSN, er endringen på disk allerede.

Til sammen lar disse feltene ARIES vite nøyaktig hva som er på disk, hva som mangler, og hva som må reapplies eller reverseres etter et krasj.

Pensum: Kap. 8 — LSN-feltene

Kap. 8 — Checkpoints Middels

Hva er en fuzzy checkpoint i ARIES, og hvorfor er den «fuzzy» — i kontrast til en sharp checkpoint?

Sharp checkpoint: stopp alle transaksjoner, flush hele bufferet til disk, skriv en checkpoint-post. Garantert konsistent disk-state, men blokkerer alt i mange sekunder for store buffere — uakseptabelt i produksjon.

Fuzzy checkpoint (ARIES): ikke flushet, ikke konsistent — bare en snapshot av metadata:
• Skriv BEGIN_CHKPT-loggpost.
• Skriv END_CHKPT med kopi av aktuell Transaction Table + Dirty Page Table.
• I master record (egen disk-lokasjon): peker til BEGIN_CHKPT.

Transaksjoner kjører videre under hele prosessen.

Recovery starter analyse-fasen fra denne BEGIN_CHKPT. Det er TT og DPT vi finner som er utgangspunktet — de sannsynligvis litt utdaterte, men oppdateres ved å lese loggen videre.

Trade-off: fuzzy checkpoints påvirker neppe ytelsen, men recovery må fortsatt prosessere alt fra begin-checkpoint og fremover.

Pensum: Kap. 8 — Checkpoints

Kap. 8 — Presedensgraf Vanskelig

Schedule: r1(A); r2(A); w1(B); w2(B); r1(C); w2(A). Er dette konfliktserialiserbart?

  • A Ja, ekvivalent med T1 → T2.
  • B Ja, ekvivalent med T2 → T1.
  • C Nei, presedensgrafen har sykel.
  • D Ja, kan velge enten rekkefølge.

Riktig: A — konfliktserialiserbart, ekvivalent med T1 → T2.

Identifiser konflikter (ulike T, samme objekt, minst én write):

w1(B) før w2(B) → kant T1 → T2 (w-w på B).
r1(A) før w2(A) → kant T1 → T2 (r-w på A).
r2(A) før w2(A) — samme transaksjon, ikke konflikt.
r1(A) og r2(A) — to reads, ikke konflikt.

Presedensgraf: T1 → T2. Asyklisk → konfliktserialiserbart. Eneste topologiske sortering: T1 før T2.

B ville krevd kant T2 → T1, men det finnes ingen konflikt der T2 kommer først.
C: ingen sykel.
D: bare én sortering er gyldig.

Pensum: Kap. 8 — Presedensgraf

Kap. 8 — Phantoms Vanskelig

Hvorfor kan ikke ren rad-låsing hindre phantom reads, og hvilke to mekanismer brukes typisk i stedet?

Rad-låser kan bare låse eksisterende rader. Et phantom-problem oppstår når en ny rad insertes som matcher et tidligere predikat — det fantes ingen rad å låse.

Eksempel: T1 kjører SELECT COUNT(*) FROM Emp WHERE dept = 5 og får 3. T2 inserterer en ny ansatt med dept = 5 og committer. T1 kjører samme SELECT igjen, får 4. Selv med S-lås på de tre opprinnelige radene har ikke T1 hindret T2 fra å sette inn en ny.

To løsninger:

1. Predicate locking: lås predikatet selv (dept = 5) — enhver INSERT/UPDATE som matcher predikatet må vente. Konseptuelt rent, men dyrt å sjekke (krever predikat-evaluering).

2. Index-range locking: hvis spørringen bruker en B+-indeks på dept, lås intervallet [5, 5] i indeksen. INSERT med dept = 5 må navigere ned i indeksen, treffer låsen, blokkerer. Standard i MySQL InnoDB, PostgreSQL.

I praksis bruker DBMS-er index-range locking. Hvis ingen indeks finnes, faller man tilbake til tabell-lås — koster mer i samtidighet.

Pensum: Kap. 8 — Phantoms

Kap. 8 — Winners og losers Middels

Etter analyse-fasen i ARIES, hvordan klassifiseres transaksjoner som winners versus losers?

Winner: transaksjon T som har en commit-post i loggen før krasjet. Endringene skal redo-es; T skal ikke reverseres.

Loser: transaksjon T som var aktiv ved krasjet — har skrevet write-poster, men ingen commit-post. Endringene må undo-es.

Etter analyse: transaction table-en inneholder bare losers. Winners har allerede en commit-post og er fjernet fra TT i analyse-passet.

REDO-fasen redo-er alle loggposter som matcher kriteriene — også fra losers, fordi vi vil at disk-bildet skal samsvare med loggen før UNDO. UNDO-fasen reverserer så loser-postene fra slutten av loggen og bakover, og skriver CLR for hver.

Etter recovery: winners er reflektert på disk; losers er som om de aldri kjørte. Database er konsistent.

Pensum: Kap. 8 — Analyse-fasen

Kap. 7 — Aksessveg-valg Vanskelig

Tabellen Order har 100 000 rader, 1500 blokker (heap), og en unclustered B+-indeks på kundeId med høyde 3. V(kundeId, Order) = 1000. Spørringen: SELECT * FROM Order WHERE kundeId = 42. Forventet kostnad?

  • A 1500 blokker (full scan)
  • B 4 blokker (3 indeks + 1 heap)
  • C ~103 blokker (3 indeks + 100 heap-treff)
  • D 1 blokk (direkte hash)

Riktig: C — ~103 blokker.

Selektivitet for likhet: sf = 1/V = 1/1000 = 0.001.
Forventet antall treff: 100 000 · 0.001 = 100 rader.

Med unclustered indeks ligger de 100 radene på 100 forskjellige heap-blokker (worst case, men det er det som regnes i pensum).

Total: 3 (B+-traversering) + 100 (heap-aksesser) = 103 blokker.

Sammenligning: full scan = 1500 blokker. Indeksen vinner — knapt — fordi treffmengden er liten nok til at random heap-aksesser ikke koster mer enn full scan.

A: full scan ville vært dyrere.
B: ville stemt for clustered eller for et unikt predikat med ett treff. Her er det ~100 treff.
D: forutsetter en hash-indeks med null kollisjon — vi har et B+-tre.

Hvis V hadde vært 10 (sf = 0.1, 10 000 treff), ville full scan vunnet med god margin.

Pensum: Kap. 7 — Aksessveger

Kap. 7 — Pipelining Vanskelig

Hva er forskjellen på pipelined og materialized evaluering av en plan, og når må optimizeren materialisere?

Pipelined: hver operator i plantreet sender rader videre til neste operator én om gangen, uten å lagre mellomresultatet. Ofte implementert som iterator-modellen (next()-kall). Lavt minneforbruk, lav latens for første rad.

Materialized: en operator beregner hele mellomresultatet og lagrer det (i RAM eller på disk) før neste operator starter. Brukes når neste operator ha hele input-en.

Tvunget materialisering:

Sortering (ORDER BY, sort-merge join build) — må se alle rader før output.
Hash join build-fase — hele build-relasjonen partisjoneres før probe.
Aggregering med GROUP BY hvis output ikke er pre-sortert eller hashet.
DISTINCT uten sortert input.
Subquery uten korrelasjon som blir kjørt én gang.

Generelt: blokkerende operatorer materialiserer; ikke-blokkerende pipeliner. Plan-trær med mange blocking nodes har høy minne-/disk-bruk og høy latens for første rad.

Pensum: Kap. 7 — Plan-utføring

Kap. 7 — Filter pushdown Middels

Hva betyr filter pushdown, og hvorfor er det en av optimizers viktigste teknikker?

Filter pushdown = utfør WHERE-predikater så tidlig som mulig i plantreet, før joins og aggregering.

Eksempel: SELECT * FROM A JOIN B ON A.id = B.aid WHERE A.year = 2020.

• Naivt plantre: σ_year=2020(A ⋈ B) — joiner først, filtrerer etter. Dyrt hvis A og B er store.
• Optimalisert: σ_year=2020(A) ⋈ B — filtrerer A først, joiner bare det som overlever.

Hvorfor virker det: |σ(A)| ≤ |A|. Mindre input til join → færre rader å sammenligne, mindre data å materialisere/sortere/hashe i join-algoritmen.

Optimalisatoren gjør dette via relasjonsalgebra-ekvivalenser (kommutativitet av σ med ⋈ når predikatet bare bruker attributter fra én side). Også projection pushdown — eliminer kolonner tidlig.

Push-down stoppes hvis predikatet bruker kolonner fra begge sider av join-en — da må joinen utføres først.

Pensum: Kap. 7 — Algebra-omskriving

Tverrgående — Indeks-bivirkninger Vanskelig

Hvilke tre kostnader betaler man for å legge til en indeks, og hvorfor er flere indekser ikke alltid bedre?

1. Lagringsplass: en B+-indeks på en kolonne lagrer 16 byte (eller mer for sammensatte) per rad — kan være 30-100 % av tabellens størrelse for store tabeller med flere indekser.

2. Skrive-overhead: hver INSERT/UPDATE/DELETE må oppdatere alle relevante indekser. En tabell med 5 indekser har 5× skrive-amplifikasjon. INSERT som ville vært én blokk-skriving blir 5+1 = 6 (eller flere ved kaskaderende splitt).

3. Optimizer-overhead og feilvalg: flere indekser betyr flere kandidatplaner. Optimizeren kan velge en suboptimal indeks hvis statistikken er dårlig — eller en spørring som ville matchet en av indeksene 100 % perfekt blir ikke valgt fordi en annen ser billigere ut.

Tommelfingerregel: indekser kolonner som brukes ofte i WHERE eller JOIN og som er selektive. Indekser ikke kolonner med få distinkte verdier (f.eks. kjønn, status) — selektiviteten er for lav.

Et OLTP-system med tunge skrivelaster bør ha få, gode indekser; et OLAP-system med sjeldne writes og mange spørringer kan ha mange.

Pensum: Kap. 6B + Kap. 7

Tverrgående — RA → SQL → plan Middels

Følg en spørring fra SQL-tekst til disk-blokker — hvilke kapitler eier hvilket steg?

1. SQL-tekst (Kap. 3): brukeren skriver et SELECT.

2. Parser → relasjonsalgebra-tre (Kap. 2): oversetter SQL til σ/π/⋈/etc., med tabeller og kolonner slått opp i SQL-katalogen.

3. Algebra-ekvivalenser (Kap. 7): push-down filtere, omorganiser joins. Fortsatt et logisk tre.

4. Cost-based optimizer (Kap. 7): generer fysiske planer med konkrete aksessveier (J1–J4, full scan, indeks-lookup, sort) og kostnadsregn dem mot statistikk i SQL-katalogen.

5. Executor (Kap. 7): kjør valgt plan ved å kalle aksessmetoder.

6. Buffer pool (Kap. 6A): oversett blokk-forespørsler til faktisk disk-I/O eller bufferhit.

7. Disk (Kap. 6A/B): heapfil eller B+-tre returnerer blokker.

Parallelt: concurrency control (Kap. 8 §1–2) låser eller bruker MVCC; recovery manager (Kap. 8 §3) logger writes via WAL.

Schemaet for hele kurset er denne pipelinen — alle øvinger og eksamener er drilling på enkeltledd.

Pensum: Reisen gjennom DBMS-en

Tverrgående — ACID i praksis Vanskelig

Hvilke konkrete DBMS-mekanismer gir oss hver av A, C, I, D? Pek på modulene fra kap. 6–8.

A — Atomicity: UNDO-logging (kap. 8). En aktiv transaksjon kan rulles tilbake fordi WAL har lagret før-bildene; recovery undo-er ufullførte transaksjoner.

C — Consistency: integritetsregler (FK, CHECK, NOT NULL, UNIQUE — kap. 3) håndheves av executoren ved hver INSERT/UPDATE/DELETE. Triggere kan utvide. Selve transaksjonen må være korrekt skrevet.

I — Isolation: concurrency control (kap. 8 §2). Strict 2PL eller MVCC (snapshot isolation) sikrer at samtidige transaksjoner ser ut som en serialiserbar utførelse.

D — Durability: WAL + force-log-at-commit (kap. 8 §3). Selv om sider ligger i bufferet ved commit, vil REDO-fasen rekonstruere committed endringer fra loggen etter krasj.

Knytningen er ikke 1:1 — UNDO bidrar også til konsistens (rollback ved constraint-brudd), og MVCC bruker også versjonering for å hjelpe atomicity. Men de fire mekanismene over er hovedansvarlige for hver bokstav.

Pensum: Kap. 8

Tverrgående — Hvorfor få nivåer i et B+-tre? Vanskelig

Hvorfor er en B+-traversering med høyde 3 raskere enn en hash-tabell med stor overflow-kjede, selv om hash-en teoretisk er O(1)?

O-notasjon ignorerer konstantfaktorer, men i praksis er antallet diskaksesser det som dominerer.

B+ med høyde 3: 3 diskaksesser garantert. Rotnoden ligger ofte i bufferet (frequently accessed) — i praksis 2 reads.

Hash med overflow-kjede på 5: 5 diskaksesser. «O(1)» er teoretisk gjennomsnitt over godt fordelt input, men når dataene har vokst forbi N, vokser kjeden lineært.

Hash uten overflow: 1 diskaksess — vinner over B+. Det er derfor extendible hashing er attraktiv for likhetsoppslag når man kan dimensjonere godt.

Hash taper imidlertid på range-spørringer: hash-en mister ordning. WHERE x BETWEEN 5 AND 10 krever full hashtabell-scan. B+-tre gjør dette i ett tre-traversal + linkliste-traversering på blader.

I et virkelig DBMS ser du derfor:
• B+-trær for primær- og sekundærindekser (default).
• Hash-indekser tilbudt som opsjon for ren-likhet (PostgreSQL har USING HASH; MySQL bruker B+ alltid på InnoDB).
• Extendible hashing internt i page directory og partition hash join.

Pensum: Kap. 6B + Kap. 6A — Hashing