Side 1 for Databaser DBS21 Samtidighetskontrollteknikker mandag 30. mai 2016 21.25 Pensum: 21.1, side 781-792, og 21.3 side 795-796 tom 21.3.1 21.1 Tofaselåsingsteknikker for samtidighetskontroll 21.1.1 Typer lås og systemlåstabeller Det finnes flere forskjellige typer låser. Binære låser. En binær lås kan ha to tilstander/verdier: låst og åpen (1 og 0). Hvert dataelement X har en distinkt lås, og hvis den er låst kan man ikke få tilgang til elementet. Man kan gjøre to operasjoner med en binær lås - lock_item og unlock_item. Hvis man ber om tilgang til X kalles lock_item, og hvis den allerede er låst må man vente på tur. Når man er ferdig, kalles unlock_item. Kan implementeres med en låsetabell som består av poster med tre felter (data_item_name, LOCK, locking_transaction), der det å være i tabellen betyr at dataelementet er låst. Transaksjoner må følge følgende regler: 1) Kall lock_item før read eller write. 2) Kall unlock_item etter read eller write. 3) Ikke kall lock_item hvis den allerede har låst elementet. 4) Ikke kall unlock_item hvis den ikke har låst elementet. Delt/ekslusiv (read/write) lås Det er tre operasjoner: - Read_lock(X) - Write_lock(X) - Unlock(X) Flere transaksjoner kan kalle read_lock(x), men kun én lås kan kalle write_lock(x). Implementasjon: Låsetabellen har poster med fire felt, og kun poster for låste dataelementer: (data_item_name, LOCK, no_of_reads, locking_transactions(s))
Side 2 for Databaser En transaksjon må følge følgende regler: 1) Read_lock(X) må kalles før read(x). 2) Write_lock(X) må kalles før write(x). 3) Unlock(X) må kalles etter alle operasjoner 4) En transaksjon vil ikke kalle read_lock(x) eller write_lock(x) hvis den allerede holder på en lås (disse kan tøyes). 5) En transaksjon vil ikke kalle unlock(x) med mindre den holder på en lås. Konvertering (oppgradering, nedgradering) av låser. Noen ganger vil man oppgradere eller nedgradere låser, kalt låskonvertering. Det er hvis en transaksjon har read_locked et dataelement og vil endre til write_locked eller motsatt (write_locked -> read_locked). Da må man "relaxe" betingelse 4. 21.1.2 Garantere serialiserbarhet med tofaselåsing, 2PL En transaksjon følger tofaselåsingsprotokollen hvis alle låseoperasjoner (read_lock, write_lock) gjøres før den første unlock-operasjonen i transaksjonen. Transaksjonen kan deles opp i to faser: Expanding, growing, første fase: Når man kan låse, men ikke låse opp. Her kan man oppgradere låser. Shrinking, andre fase: Når man kan låse opp, men ikke låse. Her kan man nedgradere.
Side 3 for Databaser Hvis alle låser i en historie følger 2PL, så er historien garantert å være serialiserbar. Selv om 2PL garanterer serialiserbarhet, betyr det ikke at alle mulige serialiserbare historier fungerer sammen med 2PL. 2PL kan også hindre samtidighet, ved at en transaksjon låser dataelementer den ikke bruker, når andre transaksjoner trenger dem. Basic 2PL Som beskrevet over. Konservativ 2PL Alle dataelementer må låses før transaksjonen begynner, ved å forhåndsdeklarere alle elementer som skal leses og skrives. Den vil vente til alle elementer er tilgjengelig for låsing. I denne protokollen kan det ikke oppstå deadlocks, vranglåser. Strikt 2PL Denne garanterer strikte historier. Den vil ikke slippe skrive-låser før etter at den har committet eller abortert, slik at ingen andre transaksjoner kan lese eller skrive disse dataelementene (som er betingelsen på en strikt historie). Denne er ikke vranglåsfri. Rigorous 2PL Denne garanterer også strikte historier, men den vil ikke slippe noen låser, både skrive- og leselåser, før etter at den har committet eller abortert. Låsing har generelt høy overhead, siden hver lese- og skriveoperasjon følger etter en systemlåsings-request. 21.1.3 Vranglås og starvation Vranglås skjer når alle transaksjoner T i en mengde med to eller flere transaksjoner, venter på et element som som er låst av en annen transaksjon T' i mengden. For eksempel, hvis T venter på X, som er låst av T', og T' venter på Y som er låst av T. Vranglåsforhindringsprotokoll Låser alle elementer som trengs på forskudd - hvis man ikke får låst alle, låses ingen. Dette er brukt i konservativ 2PL. Bruk av transaksjons-timestamps. Hver transaksjon får et timestamp basert på når det startet. Wait-die. En eldre transaksjon får lov til å vente på en yngre transaksjon, men en yngre transaksjon som prøver å låse et element holdt av en eldre transaksjon blir abortert og restartet. Wound-wait. Omvendt. En yngre transaksjon får lov å vente på en eldre, men den eldre er kjip og gjør at den yngre transaksjonen må abortere og restarte. I begge tilfeller blir den yngre transaksjonen restartet med den originale timestamp-en, og begge sørger for at det ikke blir vranglås. Men transaksjoner kan bli restartet unødvendig. Ikke vent-algoritme Hvis en transaksjon ikke får låse et element, restarter den med en gang, og venter en liten stund, i stedet for å vente. Dette kan føre til unødvendig restarting. Forsiktig venting-algoritme En transaksjon får bare lov til å vente på at en annen transaksjon slipper en lås, hvis den andre transaksjonen ikke selv venter på en transaksjon. Vranglåsoppdagelse
Side 4 for Databaser I stedet for å unngå vranglåser kan man oppdage de når de skjer. Dette passer best hvis transaksjonene er korte og bare låser noen få dataelementer om gangen, slik at det er lite interferens mellom transaksjonene - at de sjeldent bruker samme dataelementer. Et eksempel er å bruke en "wait-for"-graf, der en node blir opprettet for hver transaksjon. Hvis en transaksjon Ti venter på en lås fra en transaksjon Tj, opprettes en rettet kant fra Ti til Tj. Hvis det finnes en sykel i grafen, finnes også en vranglås. Hvor ofte bør det sjekkes etter sykler? Det kan sjekkes når et antall transaksjoner kjøres samtidig eller hvis flere transaksjoner har ventet på en lås over en lenger periode. Å sjekke for hver gang en kant legges til kan føre til overflødig overhead. Hvis systemet er kommet i vranglås, må man velge hvilken transaksjon som skal aborteres, kalt "victim selection". Det er greit å ikke velge transaksjoner som har kjørt en stund og utført mange operasjoner, men heller yngre transaksjoner som ikke har gjort så mye. Man må også sjekke om sykelen forsvant ved å abortere transaksjonen. Timeouts. En annen nyttig måte å behandle vranglåser på, som har lav overhead. Hvis en transaksjon har ventet en gitt tid, aborteres den, uavhengig av om den er i vranglås eller ikke. Starvation. Når en transaksjon ikke får gjort seg ferdig over en lenger periode, mens andre transaksjoner utføres som normalt. Løsninger: - First-come-first-served-kø, transaksjoner får låse et element i rekkefølgen som låsen ble forespurt. - Øker prioriteten til en transaksjon jo lenger den venter - Gi høyere prioritet til transaksjoner som har blitt abortert flere ganger, hvis de gang på gang blir "victim selected". Wait-die og wound-wait unngår starvation siden de restarter transaksjonen med den originale timestamp-en. 21.3 Multiversjonsamtidighetskontrollteknikker Protokoller som beholder kopier av gamle verdier til dataelementer når elementene blir oppdatert (skrevet) kalles multiversjons CC-teknikker. Når transaksjoner ber om å lese et dataelement, kan den lese en eldre versjon for å opprettholde serialiserbarheten. Ulempen med multiversjonsteknikker er at man trenger mer lagringsplass for å opprettholde flere versjoner av dataelementene. 21.3.1 Multiversjonsteknikk basert på tidsstempelordning Man har flere versjoner, X1, X2,, Xk, for hvert dataelement X, og for hver versjon Xi har man to tidsstempel: 1) 2) Read_TS(Xi). Lesetidsstempel. Største tidsstempel for en transaksjon som har lest den. Write_TS(Xi). Skrivetidsstempel. Tidsstempelet for transaksjonen som skrev verdien. Hvis X blir oppdatert av transaksjonen T, blir det laget en ny versjon Xk+1 med lesetidsstempel og skrivetidsstempel lik tidsstempelet til T. Hvis en annen transaksjon T' leser versjonen Xk+1, blir lesetidsstempelet satt til den høyeste av T og T'. For å sørge for serialiserbarhet, blir følgende regler fulgt:
Side 5 for Databaser 1) 2) Hvis transaksjon T ønsker å kjøre en write_item(x)-operasjon, og versjon i av X har det høyeste skrivetidsstempelet av alle X's versjoner, som er mindre enn eller lik tidsstempelet til T, ts(t), og lesetidsstempelet til Xi er større enn ts(t), skal transaksjonen aborteres og rulles tilbake. Ellers: opprett en ny versjon Xj av X med lesetidsstempel = skrivetidsstempel = ts(t). Hvis transaksjon T ønsker å kjøre en read_item(x)-operasjon, finn den versjonen i av X som har det høyeste skrivetidsstempelet som også er mindre enn eller lik ts(t), returner deretter verdien av Xi til transaksjon T og sett verdien av read_ts(xi) til den største av ts(t) og det nåværende lesetidsstempelet.