# DEL 1: Normalisering Et firma som leier ut feriehus lagrer følgende informasjon knytet til utleie: ``` kundedata: navn, adresse, telefon data om eiendommen: adresse data om eieren: navn, adresse, telefon data om utleieforholdet: fra og med uke, til og med uke, pris pr uke ``` Gitt at vi har én tabell med 13 attributter (kolonner) som ser altså slik ut (skrevet på relasjonell form uten nøkler): __leieforhold (kunde_id, kunde_navn, kunde_adresse, kunde_tlf, eiendom_id, eiendom_adresse, eier_id, eier_navn, eier_adresse, eier_tlf, fra_uke, til_uke, pris)__ I tillegg til dataene nevnt innledningsvis er det lagt inn kunde_id, eier_id og en eiendoms_id, som entydig identifiserer henholdsvis en person og en eiendom. #### Foreslå kandidatnøkler for denne tabellen. Anta at en person kun kan leie en eiendom av gangen, og at en eiendom kan leies ut til kun en person av gangen. > For å unikt identifisere et leieforhold kan man for eksempel bruke supernøkkelen (eiendoms_id, fra_uke). > > Dette er mulig fordi den samme eiendommen bare kan leies ut til en person av gangen. Altså kan man definitivt finne den ene tuppelen som passer disse to nøklene, og derfra utlede til_uke, pris og all data om eieren og kunden. > Riktignok setter ikke databasen noen begrensning på at samme kunde ikke kan leie flere eiendommer samtidig. Dette må sjekkes i kode eksternt uansett, siden det ikke er nok å sjekke likhet mellom endepunktene. #### Tabellen er ikke problemfri mht til registrering og sletting av data. Forklar hvorfor. > Mye av dataen vil dupliseres i denne tabellen, for eksempel om en kunde leier flere eiendommer, samme eiendom flere ganger, eller om en eier har flere eiendommer. > Dersom data skal endres, for eksempel adresse/navn/telefonnummer til enten eier eller kunde, kan dataen måtte endcres mange steder. > Dersom man sletter en eiendom, og det er den eneste eiendommen fra en gitt eier, så vil all dataen om eieren slettes. > Det samme gjelder flere permutasjoner av de faktiske entitetene (leieforhold, eier, kunde, eiendom), at om man vil slette en kan man måtte slette flere. #### Tegn ett (kun ett) diagram (tilsvarende figur som i læreboka kap. 3) som viser funksjonelle avhengigheter mellom alle attributtene. Figuren viser avhengighetene slik den er i den opprinnelige tabellen. Blå piler avhenger av hele primærnøkkelen(blått rektangel), og svart pil avhenger av enkeltattributter eller enkeltnøkler. ![Figur 1.1 ](figur_1_1.png) ### Du skal nå bruke funksjonelle avhengigheter og BCNF til å foreslå en oppsplitting av tabellen i mindre tabeller slik at problemene vedr. registrering og sletting av data unngås. Sett opp, direkte fra figuren, relasjoner som er på BCNF. __Kan vi løse denne oppgaven ved å gjennomføre prosessen 1NF --> 2NF --> 3NF? Begrunn svaret.__ Gitt at vi ikke trenger spørringer basert på deler av adresse (feks gruppering av eiendommer basert på postnummer), er databasen allerede på 1NF. For å få den over til 2NF trenger vi da å fjerne funksjonelle avhengigheter fra **deler av** primærnøkkelen. I dette tilfellet ser vi at eiendom_adresse og all data om eier følger av eiendom_id. Derfor trenger vi å dele denne fra en til tre tabeller. #### Forslag på 2NF: ![Figur 2NF](2NF.png) Her er det ingen partielle avhengigheter, altså avhenger ingen attributter av __deler__ av primærnøkkelen. Med 2NF er noen av de tidligere problemene løst, for eksempel er det mulig å endre dataen til en eier (navn,tlf,adresse) ved å bare gjøre endringer ett sted. Det er også mulig å slette eiendommer uten fare for å slette data om eieren. Altså er flere problemer ved både innsetting, redigering og slettet fikset, men for eksempel problemer tilknyttet å redigere en kunde er fortsatt tilstede. For eksempel er det ikke sikkert at man kan endre adressen til en kunde uten flere endringer. Den bryter fortsatt 3NF, fordi kunde_navn, adresse og telefon avhenger av kunde_id. #### Forslag på 3NF ( /BCNF ): ![Figur 3NF](3NF.png) Her er avhengigheten fra kunde_id til de andre kundeattributtene flyttet ut til en egen tabell, slik at alle f.d. utgår av primærnøkkelen. Dette er kravet for 3NF. I dette tilfellet kan man både sette inn og endre kunder, eiere og eiendommer uten at de påvirker hverandre. For eksempel kan man registrere en ny kunde, uten at den har leid noe enda. Sletting avhenger av cascading i tabellen, og er ikke garantert å virke uten sideeffekter. Man kan alltid slette leieforhold. Kunder kan slettes om de ikke har noen leieforhold. Eiendommer kan slettes om de ikke er i et leieforhold. Eiere kan slettes om de ikke har noen eiendom. Her har jeg altså vist at man kan ta denne tabellen fra 1NF til 3NF via 2NF. Dette er ikke gitt for alle relasjoner, men det fungerer for denne. Skrevet på relasjonsform: ``` kunde(id*, navn, adresse, telefon) eier(id*, navn, adresse, telefon) eiendom(id*, adresse, eier_id) leieforhold(eiendom_id*, fra_uke*, kunde_id, til_uke, pris) ``` # DEL 2: Transaksjoner ## Teori ### Hvilke typer låser har databasesystemene? - __Delt lås__ - Flere klienter kan ha flere låser samtidig - Tillater at andre kan lese dataen, men ikke skrive underveis - __Eksklusiv lås__ - Kun en transaksjon kan holde låsen til samme tid - Må vente til ingen andre har lås på objektet - Andre kan heller ikke lese ### Hva er grunnen til at at man gjerne ønsker lavere isolasjonsnivå enn SERIALIZABLE? Med SERIALIZABLE isolasjon nekter vi andre transaksjoner og uttrykk i å bruke data som har vært endret av den isolerte operasjonen. Hvis vi bruker SERIALIZABLE, og kjører et UPDATE-uttrykk, vil vi sette en ekslusiv lås slik at andre ikke kan lese innholdet. Dette beholder strengt den atomiske egenskapen i en transaksjon, ved at du enten leser helt ny eller helt gammel data (før og etter transaksjonen), aldri delvis. I praksis fører dette til mye venting på blokkerte ressurser, selv om andre bare skal lese. Med REPEATABLE READ er ny data satt inn med "INSERT" lesbart for andre, og med READ COMMITED er lesing av bekreftet endret data tillatt. I veldig mange tilfeller er dette OK, og vi trenger ikke bruke like mange ekslusive låser. ### Hva skjer om to pågående transaksjoner med isolasjonsnivå serializable prøver `select sum(saldo) from konto`? SELECT-setninger alene vil bare lage delte låser, ikke ekslusive, selv i SERIALIZABLE. Altså vil begge transaksjonene kunne lese og bruke dataen samtidig. SUM gjør også aldri endringer i dataen, og påvirker ikke låsene. ### Hva er to-fase-låsing? Når man starter en databasetransaksjon kan man tenke seg at det sikreste er å låse alle tabellene / objektene i databasen, men dette skaper unødvendig mange låser og mye venting. Fase 1: Begynn å bruke / endre data, sett stadig flere låser __ettter hvert som de trengs__. Fase 2: Commit / Rollback, __fjern alle låsene på en gang.__ Delingen holder låsingen til et minimum, uten å forstyrre normal operasjon av databasen. ### Hvilke typer samtidighetsproblemer (de har egne navn) kan man få ved ulike isolasjonsnivåer? Hva er optimistisk låsing/utførelse? Hva kan grunnen til å bruke dette være? #### Vi kan få samtidighetsproblemer som - Overskriving / kollisjon: Flere oppdateringer skjer samtidig, alle untatt den siste kan gå tapt. - Ikke-bekreftede data: En annen transaksjon bruker data som ikke er COMMIT-et. - Inkonsistent uthenting ( Non-repeatable read ): Deler av den hentede dataen er ferdig oppdatert, deler er ikke oppdatert enda. #### Optimistisk låsing/utførelse: I mange applikasjonstilfeller kan vi si det er usannsynlig at data skal settes inn eller fjernes samtidig med andre prosesser. I slike tilfeller kan man med fordel vente med å sette låser til det er nødvendig. Vi leser dataen vi kanskje planlegger å endre, men setter ikke lås enda. Etter å ha gjort arbeidet vi trenger å gjøre, sjekker vi om dataen har endret seg(sjekk en spesifikk verdi, en hash eller et timestamp, avhengig av implementasjon). Hvis den ikke har endret seg kan vi sette en eksklusiv lås og gjøre endringen vi trenger. Dersom dataen (mot formodning, ikke sannsynlig) har endret seg i mellomtiden, vil operasjonen feile og vi må prøve på nytt. I tilfeller med få kollisjoner vil dete være effektivt siden vi har låser bare når det er helt nødvendig, men det gjør at vi kan måtte gjøre arbeid flere ganger om noe skulle kollidere. Dette gjør paralellt arbeid lettere med en enklere dataflyt. ### Hvorfor kan det være dumt med lange transaksjoner (som tar lang tid)? Vil det være lurt å ha en transaksjonhvor det kreves input fra bruker? Om man bruker transaksjoner med streng isolasjon, for eksempel SERIALIZABLE, som bruker mange ekslusive låser, betyr det at alle andre operasjoner må vente i kø til transaksjonen blir bekreftet eller avbrutt. Over tid kan køen bygge seg opp, og det er alltid noe man må vente på. Derfor kan data ofte gjøres klar i forveien, for eksempel det som skal brukes i INSERT og DELETE, slik at brukerinput og lignende er samlet inn og sjekket før man setter låsen. # Oppgaver om transaksjoner ### Oppg 1: Serializable setter delt lås ved SELECT, så klient 1 blir stoppet med exception når den kjører UPDATE. Dataen blir ikke endret. Dette skjer bare ved SERIALIZABLE, det strengeste isolasjonsnivået. ### Oppg 2: a) Den siste dataen vil commites, og dermed lagres, til sist. Det tilsvarer endringene fra klient 2. | **kontonr** | **saldo** | |-------------|-----------| | 1 | 2 | | 2 | 2 | b) I dette tilfellet vil vi se en deadlock, fordi klient 1 setter ekslusiv lås på kontonr 1, og klient 2 setter ekslusiv lås på kontonr 2. Deretter vil k1 vente på ledig kontonr 2, og k2 vil vente på ledig kontonr 1, uten at noen kan bli ferdige. Alle isolasjonsnivåer setter ekslusiv lås på UPDATE, så det vil ikke ha noen effekt. ### Oppg 3: #### Hva skjer? Klient 1 vil se initialverdien ved første select, og den nye, større, verdien i de to siste. ### Hva vil skje om Klient 1 bruker read committed, repeatable read eller serializable? Serializable: Serializable isolasjonsnivå vil sette en delt lås ved SELECT, og derfor vil ikke klient 2kunne skrive endringer før transaksjonen er slutt. Repeatable read / read committed: Klient 1 vil se initialverdien ved de to første select, og den nye, større verdien i den siste. ### Oppgave 4 #### Lag en kjøring med to klienter som tester phantom reads. Her kan det være lurt å tenke igjennom isolasjonsnivå. Om resultatet ikke er som forventet så kan det være lurt å sjekke dokumentasjonen. Begge klienter: ```sql SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; START TRANSACTION; ``` k1: > SELECT * FROM konto WHERE kontonr > 3; viser konto 4-7 k2: > INSERT INTO konto SET kontonr=8, saldo=120; k1: > SELECT * FROM konto WHERE kontonr > 3; viser konto 4-7 k2: > COMMIT; k1: > SELECT * FROM konto WHERE kontonr > 3; viser konto 4-7 k1: > COMMIT; > SELECT * FROM konto WHERE kontonr > 3; viser konto 4-8 Altså viser ikke min databaseserver(MariaDB med InnoDB) noen tegn til phantom read, selv om isolasjonsnivået ( REPEATABLE READ) skulle tilsi det, basert på tabell i foiler fra foreleser.