Bankovní institut vysoká škola Praha Katedra matematiky, statistiky a informačních technologií Řízení souběžného přístupu k datům v systémech řízení báze dat Bakalářská práce Autor: Petr Havlas Informační technologie, správce informačních systémů Vedoucí práce: Ing. Michal Valenta, Ph.D. Praha Červen, 2012
Prohlášení: Prohlašuji, ţe jsem bakalářskou práci zpracoval samostatně a v seznamu uvedl veškerou pouţitou literaturu. Svým podpisem stvrzuji, ţe odevzdaná elektronická podoba práce je identická s její tištěnou verzí, a jsem seznámen se skutečností, ţe se práce bude archivovat v knihovně BIVŠ a dále bude zpřístupněna třetím osobám prostřednictvím interní databáze elektronických vysokoškolských prací. V Praze, dne 25.6.2012... Podpis autora
Poděkování: Děkuji Ing. Michalu Valentovi Ph.D. za podporu a odborné vedení během mé práce.
Anotace Bakalářská práce Řízení souběţného přístupu k datům v systémech řízení báze dat je zaměřena na představení základních mechanismů pouţívaných pro řízení paralelního přístupu k datům v systémech řízení báze dat a provádí porovnání implementace v databázovém systému Oracle a PostgreSQL. Klíčová slova Řízení souběţného přístupu, dvoufázové zamykání, multiversion concurrency control, transakce, uváznutí, uspořádatelnost rozvrhu, ekvivalence rozvrhu Annotation Bachelor s thesis Concurrency control in database management systems is dealing with basic technics used in database management systems and compare implementation of these technics in Oracle and PostgreSQL databases. Key words Concurrency control, two-phase locking, multiversion concurrency control, transaction, deadlock, schedule serializability, schedule equivalence
Obsah 1 ÚVOD... 6 1.1 TRANSAKCE... 6 1.2 USPOŘÁDATELNOST... 7 1.2.1 Rozvrhovač a rozvrh transakcí... 7 1.2.2 Uspořádatelný versus sériový rozvrh... 9 1.3 IZOLACE TRANSAKCÍ... 12 1.3.1 Anomálie... 13 1.3.2 Úroveň izolace podle SQL standardu... 14 1.4 ŘÍZENÍ SOUBĚŢNÉHO PŘÍSTUPU... 15 1.4.1 Optimistické a pesimistické metody řízení souběžného přístupu... 15 1.5 UVÁZNUTÍ... 16 1.5.1 Prevence... 16 1.5.2 Timeout... 19 1.5.3 Detekce a zotavení... 19 2 METODY POUŽÍVANÉ PRO ŘÍZENÍ SOUBĚŽNÉHO PŘÍSTUPU... 22 2.1 ZÁMKY... 22 2.1.1 Dvoufázové zamykání (2PL)... 23 2.1.2 Strukturované zamykání... 25 2.2 TIMESTAMP ORDERING... 26 2.2.1 Thomas Write Rule... 28 2.3 MULTIVERSION CONCURRENCY CONTROL... 28 2.3.1 Multiversion timestamp ordering... 31 2.3.2 Multiversion 2PL... 31 2.4 VALIDATION-BASED CONCURRENCY CONTROL... 32 3 ORACLE RDBMS... 34 3.1 IMPLEMENTACE CONCURRENCY CONTROL V RDBMS ORACLE... 34 3.1.1 Zámky... 34 3.1.2 MVCC a Undo segment... 38 3.2 ÚROVNĚ IZOLACE V ORACLE DATABÁZI... 40 3.3 CHYBY SPOJENÉ SE SOUBĚŢNÝM PŘÍSTUPEM... 45 4 POSTGRESQL... 48 4.1 IMPLEMENTACE CONCURRENCY CONTROL V POSTGRESQL... 48 4.1.1 Zámky... 48 4.1.2 Implementace MVCC... 51 4.2 ÚROVNĚ IZOLACE V POSTGRESQL... 53 5 ZÁVĚR... 59 5
1 Úvod Tato práce se zabývá problematikou řízení souběţného přístupu (concurrency control) v databázovém stroji, jejím cílem je přinést přehled základních technik, postupů a protokolů - první část obsahuje úvod do problematiky, přehled základních termínů z oblasti transakčního zpracování a řízení souběţného přístupu, druhá část je jiţ zaměřena na konkrétní protokoly vyuţívané pro řízení souběţného přístupu a poslední část práce se věnuje implementaci ve vybraných relačních databázích pro porovnání jsem vybral RDBMS Oracle, jako jednoho z hlavních představitelů komerčního databázového software a PostgreSQL, jako zástupce pokročilých Open source řešení. 1.1 Transakce Transakce je obvykle definována jako logická jednotka databázového zpracování, obsahuje jednu nebo více operací přístupu k databázi (vloţení, smazání, modifikace, a nebo čtení dat). Pro zajištění integrity dat musí transakce splňovat tzv. ACID vlastnosti, kde ACID jsou první písmena vlastností Atomicity (atomičnost), Consistency (konzistence), Isolation (izolovanost) a Durability (trvalost). Atomicita určuje, ţe transakce je vţdy vykonána jako jedna operace, jako celek. Znamená to tedy, ţe všechny operace v transakci jsou buď úspěšně provedeny, nebo se neprovede ţádná - účelem je zajistit konzistentní data v případě poruchy databázového stroje. Konzistence zajišťuje, ţe pokud je databáze konzistentní před započetím transakce, úspěšné provedení transakce uvede databázi opět do konzistentního stavu. Toto je zodpovědností vývojáře nebo uţivatele, který transakci sestavuje. Izolovanost umoţňuje paralelní provádění transakcí, musí zajistit, ţe výsledek paralelně prováděných transakcí bude stejný jako při sekvenčním zpracování, nesmí tedy docházet k ovlivňování mezi současně běţícími transakcemi. Trvalost znamená, ţe změny provedené úspěšně dokončenou transakcí jsou persistentní, nesmí dojít k jejich ztrátě při poruše databázového systému. 6
1.2 Uspořádatelnost Pro databázové zpracování je typické, ţe probíhají stovky nebo tisíce transakcí za vteřinu a je tedy velmi pravděpodobné, ţe dvě nebo více operací budou pracovat se stejnými daty. Jedním z důleţitých úkolů databázového stroje tedy je zajištění konzistence dat při souběţném zpracování, aby výsledek zpracování souběţných transakcí byl shodný jako při jejich sekvenčním průběhu. Vlastnost provést paralelní transakce tak, ţe výsledek odpovídá sériovému zpracování, se nazývá uspořádatelnost (serializability). Obecně lze uvést, ţe rozvrh transakcí je uspořádatelný, pokud je efekt změn v databázi stejný, jako v případě sériového rozvrhu. 1.2.1 Rozvrhovač a rozvrh transakcí Rozvrh (schedule) je časově uspořádaná sekvence akcí prováděná jednou nebo vice transakcemi, pro zápis rozvrhu se pouţívají pouze operace READ a WRITE. Nyní předpokládejme rozvrh S, který obsahuje transakce T i a T j, kde i j. Transakce T i obsahuje pouze jednu operaci I j, T j pouze operaci I j a všechny operace pracují se stejný datovým objektem O - existují tedy čtyři moţné kombinace, které musíme brát v úvahu: I i =READ(O), I j =READ(O) - v tomto případě nezáleţí na pořadí, protoţe v obou případech je čtený stejný objekt. I i =READ(O), I j =WRITE(O) kdyţ I i proběhne před I j, tak transakce Ti přečte obsah O ještě před jeho změnou transakcí T j. Pokud se pořadí prohodí, T i přečte hodnotu O změněnou transakcí T j - výsledek tedy záleţí na pořadí instrukcí. I i =WRITE(O), I j =READ(O) jedná se o obdobný případ jako v předchozím bodu, na pořadí záleţí. I i =WRITE(O), I j =WRITE(O) pořadí operací přímo ovlivňuje hodnotu O uloţenou v databázi, protoţe se projeví pouze poslední provedená instrukce WRITE. O operacích, jejichţ pořadí nelze vyměnit, říkáme, ţe jsou konfliktní - instrukce I i a I j jsou konfliktní, pokud patří k různým transakcím, pracují se stejným objektem a alespoň jedna z nich je instrukcí WRITE. Tabulka 1 obsahuje přehledně uspořádané všechny kombinace. 7
READ(X) WRITE(X) READ(X) Kompatibilita Konflikt WRITE(X) Konflikt Konflikt Tabulka 1: Kompatibilita operací READ a WRITE Zdroj: (9) Pokud po sobě jdoucí operace I i a I j nejsou konfliktní, můţeme jejich pořadí vyměnit a vytvořit tak rozvrh S' říkáme, ţe rozvrh S' je ekvivalentní rozvrhu S, pokud pořadí všech operací je stejné, s výjimkou těch, na jejichţ pořadí nezáleţí. Pokorný (9) potom definuje ekvivalenci (vzhledem ke konfliktům) rozvrhů S a S' pomocí dvojice podmínek: 1) Pokud se v rozvrhu S vyskytuje READ(O) v transakci T i a tato hodnota vznikla z WRITE (O) v transakci T j, potom toto musí být zachováno i v rozvrhu S'. 2) Jestliţe se v rozvrhu S vyvolává poslední operace WRITE(O) v transakci T i, pak totéţ je nutné zachovat i v rozvrhu S'. Zotavitelnost rozvrhu rozvrh nazýváme zotavitelným (recoverable) pokud v případě transakci T i a T j platí, ţe operace WRITE(X) v T i předchází operaci READ(X) transakce T j, ale potvrzení (COMMIT) transakce T j nastane dříve neţ ukončení transakce T i, pokud by potom došlo ke zrušení (ABORT) transakce T i, transakce T j uţ by byla potvrzena a nebylo by moţné obnovit konzistenci dat. Na obrázku 1 je jednoduchá ukázka nezotavitelného rozvrhu transakcí T 1 a T 2, obrázek 2 potom obsahuje tentýţ rozvrh, upravený tak, aby splňoval podmínky zotavitelnosti. T 1 T 2 WRITE(X) READ(X) WRITE(X) COMMIT ABORT Obrázek 1: Nezotavitelný rozvrh Zdroj: autor T 1 T 2 WRITE(X) READ(X) WRITE( ABORT COMMIT Obrázek 2: Zotavitelný rozvrh Zdroj: autor V některých případech můţe zrušení jedné transakce vést k nutnosti zrušit také další transakce, toto chování se nazývá kaskádové rušení (cascading aborts). Zabránit tomuto chování se dá tím, ţe transakce čte pouze potvrzené změny. Pokud je rozvrh odolný proti kaskádovému rušení, znamená to, ţe je také zotavitelný, ale pro zotavitelný rozvrh jiţ nemusí 8
platit, ţe je zároveň odolný proti kaskádovému rušení. Na obrázku 3a je zotavitelný rozvrh, ale pokud provedeme zrušení transakce T 1, musíme také provést zrušení transakce T 2, protoţe pracovala s neplatnou hodnotou X. Upravený rozvrh 3b jiţ tento problém řeší. T 1 T 2 WRITE() READ(X) WRITE(X) ABORT ABORT Obrázek 3: Kaskádové rušení Zdroj: autor T 1 T 2 READ(X) WRITE(X) WRITE(X) ABORT COMMIT a) b) 1.2.2 Uspořádatelný versus sériový rozvrh O sériovém rozvrhu mluvíme v případě, ţe všechny operace transakce T x jsou dokončeny před první operací transakce T y atd. T 1 T 2 X (X=100) Y (Y=100) READ(X); X=X+10 WRITE(X) 110 READ(Y); Y=Y-5 WRITE(Y) 95 READ(X); X=X*2 WRITE(X) 220 READ(Y); Y=Y+45 WRITE(Y) 140 220 140 Obrázek 4: Sériový rozvrh 1 Zdroj: autor T1 T2 X (X=100) Y (Y=100) READ(X); X=X*2 WRITE(X) 200 READ(Y); Y=Y+45 WRITE(Y) 145 READ(X); X=X+10 WRITE(X) 210 READ(Y); Y=Y-5 WRITE(Y) 140 210 140 Obrázek 5: Sériový rozvrh 2 Zdroj: autor Korektním rozvrhem označujeme rozvrh, po jehoţ dokončení je databáze ve stejném stavu, jako po dokončení některého sériového rozvrhu. Korektnost rozvrhu zajišťuje jeho 9
uspořádatelnost. Uspořádatelností rozvrhu rozumíme fakt, ţe rozvrh je nějakým způsobem ekvivalentní se sériovým rozvrhem (dále si představíme pohledovou a konfliktovou ekvivalenci). Platí, ţe kaţdý sériový rozvrh je uspořádatelný, ale jiţ neplatí, ţe kaţdý uspořádatelný rozvrh je sériový. O dvojici rozvrhů S a S' můţeme prohlásit, ţe jsou pohledově ekvivalentní, pokud splňují následující trojici podmínek (10): 1. Pokud transakce T i čte úvodní hodnotu objektu O v rozvrhu S, musí ji číst také v rozvrhu S'. 2. Pokud transakce T i čte v rozvrhu S objekt O zapsaný transakcí T j, tak v rozvrhu S' musí T i také číst O zapsaný transakcí T j. 3. Pro kaţdý objekt O platí, ţe transakce, která tento objekt zapsala poslední v S, musí ho poslední zapsat také v S'. Pokud je rozvrh pohledově ekvivalentní se sériovým rozvrhem, říkáme o něm, ţe je pohledově uspořádatelný. Dvojice rozvrhů je konfliktově ekvivalentní, pokud relativní pořadí konfliktních operací (viz Tabulka 1) je stejné v obou rozvrzích (12), rozvrh označujeme jako konfliktově uspořádatelný, pokud je konfliktově ekvivalentní s nějakým sériovým rozvrhem. Můţe platit, ţe korektní rozvrh není podle uvedené definice ekvivalentní s ţádným sériovým rozvrhem. Test na konfliktovou uspořádatelnost se skládá ze dvou kroků: Krok 1: zjištění konfliktních párů víme, ţe konflikt vzniká, pokud různé transakce provedou operace nad stejným objektem a alespoň jedna z těchto operací je WRITE, sledujeme tedy situace, kdy transakce Ti čte objekt, který předtím transakce Tj zapsala, transakce Ti zapisuje objekt, který předtím přečetla transakce Tj, a nebo transakce Ti zapisuje objekt, který jiţ předtím zapsala transakce Tj. 10
Mějme rozvrh S trojice transakcí T1,T2 a T3 T1 T2 T3 READ(X) WRITE(X) WRITE(X) READ(Y) WRITE(Y) WRITE(Y) WRITE(X) V rozvrhu S můţeme nalézt celkem pět konfliktních párů: 1. Transakce T2 zapisuje objekt X poté co transakce T1 tento objekt přečetla. 2. Transakce T1 zapsala X po zápisu X transakcí T2. 3. Transakce T1 čte Y, který jiţ předtím zapsala transakce T3. 4. Transakce T1 zapisuje Y poté, co Y zapsala transakce T3. 5. Transakce T3 zapisuje X po transakci T1. Krok 2: vytvoření a vyhodnocení precedenčního grafu - začneme zakreslením uzlů pro transakce T1, T2 a T3, poté jednotlivé konfliktní operace zakreslíme jako hrany orientovaného grafu. T1 RW(X) WW(X) T2 WW(Y) WW(X) WR(Y) T3 Obrázek 6: Precedenční graf rozvrhu transakcí Zdroj: autor Pokud je vytvořený graf acyklický, znamená to, ţe daný rozvrh je konfliktově ekvivalentní se sériovým rozvrhem (je konfliktově uspořádatelný). V našem případě je vytvořený precedenční graf cyklický, daný rozvrh tedy není konfliktově uspořádatelný. 11
Pokud bych měl shrnout vztah mezi vlastnostmi transakčních rozvrhů uvedenými v předchozím textu, lze to přehledně provést pomocí Vennova diagramu: všechny rozvrhy uspořádatelné rozvrhy pohledově uspořádatelné rozvrhy konfliktově uspořádatelné rozvrhy zotavitelné rozvrhy rozvrhy odolné proti kaskádovému rušení sériové rozvrhy Jak je z diagramu zřejmé, platí, ţe kaţdý sériový rozvrh splňuje všechny dříve uvedené vlastnosti je uspořádatelný (i pohledově a konfliktově), je odolný proti kaskádovému rušení a je zotavitelný. Na druhou stranu jiţ neplatí, ţe kaţdý uspořádatelný rozvrh je zotavitelný a odolný proti kaskádovému rušení, nebo ţe zotavitelný rozvrh je nutně také uspořádatelný. Obrázek 7: Vztah mezi různými druhy rozvrhů Zdroj: upraveno podle (1) a (5) 1.3 Izolace transakcí Jedním z nejdůleţitějších úkolů databázového stroje je zajistit izolaci transakcí, v této kapitole na tuto vlastnost nahlédneme z pohledu SQL standardu (19). V SQL standardu se izolace a úroveň izolace definuje za pomocí anomálií (phenomena), které databázový systém umoţňuje. 12
1.3.1 Anomálie Rozlišujeme čtyři základní druhy anomálií ztráta aktualizace (lost update), dočasná aktualizace (dirty read), neopakovatelné čtení (non-repeatable read) a fantóm (phantom read). 1.3.1.1 Ztráta aktualizace T1 a ztraceny. Ztráta aktualizace nastane v případě, ţe jedna transakce přepíše výsledek druhé. T1 READ(A); A=A+20 WRITE(A) COMMIT T2 READ(A); A=A+10 WRITE(A) COMMIT Obrázek 8: Ztráta aktualizace Zdroj: autor Jak je vidět z rozvrhu transakcí T 1 a T 2, změny transakce T 2 jsou přepsány transakcí 1.3.1.2 Dočasná aktualizace Zdrojem této anomálie je čtení nepotvrzených dat - na jednoduchém příkladu si můţeme předvést, jak k tomuto problému dojde uvaţujme dvě transakce T 1 a T 2 : T 1 sníţí obsah A o 5, následně transakce T 2 přečte obsah A dříve, neţ dojde k potvrzení transakce T 1 a navýší B o hodnotu A. T1 READ(A); A=A-5 WRITE(A) ROLLBACK T2 READ(A) READ(B);B=B+A WRITE(B) COMMIT Obrázek 9: Rozvrh s výskytem dirty read Zdroj: autor Pokud v dalším kroku dojde ke zrušení transakce T 1, nastane problém - transakce T 2 obsahuje nyní jiţ neplatná data, protoţe hodnota A se kterou pracovala, vlastně nikdy neexistovala. 13
1.3.1.3 Neopakovatelné čtení Podstatou této anomálie je čtení dat před změnou a po potvrzení této změny - transakce T 1 přečte řádek, transakce T 2 tento řádek změní a je potvrzena. Kdyţ nyní transakce T 1 přečte opět stejný řádek, dostane jinou hodnotu, neţ při prvním čtení. T1 READ(A) READ(A) COMMIT T2 READ(A); A=A+10 WRITE(A) COMMIT Obrázek 10: Rozvrh s neopakovatelným čtením Zdroj: autor 1.3.1.4 Fantóm Jedná se o podobný problém, jako v případu neopakovatelného čtení, jenom s tím rozdílem, ţe se změní počet vrácených řádků transakce T 1 přečte mnoţinu řádků, která odpovídá nějaké podmínce, potom transakce T 2 smaţe nebo vloţí řádek, který také odpovídá podmínce z transakce T 1 a je potvrzena. Pokud nyní T 1 zopakuje původní dotaz, vrátí se jí rozdílný počet řádků, neţ v prvním případu. 1.3.2 Úroveň izolace podle SQL standardu SQL standard rozlišuje čtyři úrovně izolace transakcí READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ a SERIALIZABLE. Úroveň izolace SQL transakce určuje úroveň, po jakou jsou data v této transakci ovlivňována paralelně běţícími transakcemi, a po jakou úroveň můţe tato transakce ovlivňovat data ostatních transakcí. Podle SQL standardu je ve všech čtyřech úrovních zaručeno, ţe transakce proběhne buď celá, nebo vůbec a ţe nemůţe dojít k fenoménu ztracené aktualizace. Implicitní úrovní izolace transakce je podle standardu SERIALIZABLE. Dirty reads Non-repeatable read Phantom READ UNCOMMITTED možné možné možné READ COMMITTED nemožné možné možné REPEATABLE READ nemožné nemožné možné SERIALIZABLE nemožné nemožné nemožné Tabulka 2: Úrovně izolace a fenomény podle standardu SQL Zdroj: (19) 14
1.4 Řízení souběžného přístupu Databázi lze zjednodušeně definovat také jako konečnou mnoţinu zdrojů, ke kterým paralelně přistupují uţivatelé (8). Uţivatelé interagují s databázovým systémem pomocí transakcí, coţ jak jsme si ukázali dříve, jsou sekvence operací čtení a zápisu. Transakci je v případě paralelního zpracování nutné brát nejen jako jednotku zpracování nebo práce, ale také jako jednotku konzistence, ve smyslu, ţe databázi dostává z jednoho konzistentního stavu do druhého. Řízení souběţného přístupu (Concurrency Control) má potom za úkol zajistit, ţe je konzistence zachována také při paralelním zpracování a přístupu ke zdrojům databáze. 1.4.1 Optimistické a pesimistické metody řízení souběžného přístupu Jedním z mechanismů poţívaným pro řízení souběţného přístupu jsou zámky. Transakce musí před přístupem k objektu získat zámek nad tímto objektem, příkladem těchto protokolů můţe být např. dvoufázové zamykání (podrobněji se budu těmito protokoly zabývat v kapitole 2.1). Protoţe tyto protokoly implicitně předpokládají, ţe můţe dojít ke konfliktu transakcí a předcházejí tomu tím, ţe objekt uzamknou, i kdyţ ke konfliktu nemusí dojít (a objekt uvolní, aţ kdyţ tato moţnost vyprší), označujeme je jako metody pesimistické (Pessimistic concurrency control). Tento přístup je vhodnější pro prostředí s vyšším výskytem konfliktů. Druhá skupina protokolů je postavena na předpokladu, ţe konflikt je málo častá situace a akce nutné k zajištění integrity databáze se tedy spouští pouze v případu, ţe k tomuto konfliktu dojde. Tento přístup se označuje jako optimistický (Optimistic concurrency control). Protokoly postavené na tomto přístupu neblokují ţádné operace a odsouvají test korektnosti transakce aţ na její závěr, pokud při závěrečném testu zjistí problém, transakci zruší, aby nedošlo k porušení integrity databáze. Protokoly s tímto přístupem jsou vhodné pro nasazení v systémech s malým mnoţstvím konfliktů a s vysokým podílem read-only transakcí. Příkladem tohoto přístupu jsou Validation-based protokoly (viz kapitola 2.3). 15
1.5 Uváznutí Korth a Silberschatz (11) definuje uváznutí (deadlock) jako stav systému, kde existuje taková mnoţina transakcí, ţe kaţdá transakce z této mnoţiny čeká na jinou transakci z této mnoţiny. Dále to upřesňuje - existuje mnoţina čekajících transakcí {T 0, T 1,...,T n } taková, ţe T 0 čeká na datový objekt, který drţí T 1, T 1 čeká na objekt drţený transakcí T 2,..., T n-1 čeká na objekt, který drţí T n, která čeká na objekt drţený T 0. V této situaci nemůţe pokračovat ţádná z transakcí. V okamţiku, kdy jiţ k uváznutí dojde, má databáze jedinou moţnost a to je zrušení některé z uváznutých transakcí. Existují dvě základní metody, jak se s problémem uváznutí vypořádat prvním z nich je prevence (deadlock prevention), kde se snaţíme předejít samotnému vzniku uváznutí a druhá moţnost je dopustit vznik uváznutí, následně ho detekovat a zotavit se z tohoto stavu (deadlock detection and deadlock recovery). T1 SHARE_LOCK(X) READ(X) LOCK(Y) T2 SHARE_LOCK(Y) READ(Y) LOCK(X) Obrázek 11: Jednoduchá ukázka uváznutí (deadlock) Zdroj: autor 1.5.1 Prevence Existují dva základní přístupy k prevenci uváznutí jedním z nich je zabránění vzájemnému čekání pomocí řazení poţadavků na zámky nebo vyţadováním, aby všechny zámky byly získány najednou. Druhý přístup je částečně podobný detekci uváznutí a v případě, ţe by mohlo dojít k uváznutí, provede zrušení transakce. 16
Nejjednodušší realizací prvního přístupu je poţadavek na uzamčení všech datových objektů, které transakce bude vyuţívat, ještě před začátkem jejího běhu budou tedy zamčeny všechny poţadované datové objekty, nebo ţádný. Nevýhodou této metody je například, ţe před začátkem transakce je velmi náročné predikovat objekty k uzamčení, nebo nízké vyuţití datových objektů, protoţe je mnoho z nich často zamčeno a to i po velmi dlouhou dobu. Tato metoda je pouţívána například v konzervativním dvoufázovém zamykání (Conservative 2PL, C2PL). Druhý protokol zaloţený na tomto přístupu vyţaduje setřídění všech datových objektů (např. pokud dochází k zamykání po datových blocích, můţeme pouţít třídění podle jejich fyzické adresy) a zajištění, ţe transakce zamyká objekty podle tohoto pořadí. Pokud transakce uzamkne objekt O n, jiţ nemůţe dojít k poţadavku na uzamčení objektu O n-1, který podle setřídění tomuto objektu předchází. Předpokládejme transakci T 1, která čeká na zámek objektu O 0, drţený T 0 ; T 2 čeká na zámek O 1, drţený T 1 ;...;T n čeká na zámek na objektu O n-1, drţený transakcí T n-1 a T 1 čeká na zámek objektu O n, drţený transakcí T n. Pokud T 1 drţí zámek objektu O 1 a čeká na zámek O 0, musí platit, ţe O 1 <O 0 podle pořadí objektů. Podobně O i <O i-1 pro i=2,3,...,n. Protoţe T 0 drţí zámek na O 0 a zároveň čeká na O n, tak plyne, ţe A 0 <A n. Máme tedy A 0 <A n <A n-1 <...<A 1 <A 0 a to je nemoţné, protoţe by muselo platit, ţe A0<A0 k uváznutí tedy nemůţe dojít. Jak je jiţ dříve uvedeno, ve druhém přístupu se systém rozhoduje, jestli transakci nechá čekat, nebo ji zruší. Vyuţívá se koncept časových razítek (timestamp) transakcí, která jednoznačně identifikují kaţdou transakci, typicky jsou časová razítka zaloţena na pořadí startu transakcí, takţe platí, ţe pokud transakce T i startuje před transakcí T j, potom časové razítko Ts(T i ) < Ts(T j ). Pro rozhodnutí, jestli transakci zrušit nebo nechat čekat, se vyuţívá dvojice algoritmů wait-die a wound-wait. 17
1.5.1.1 Wait-die Jedná se o nepreemtivní techniku kdyţ transakce T i datový objekt drţený transakcí T j, transakci T i bude umoţněno čekání pouze v případě, ţe její časová značka je niţší neţ časová značka transakce T j, tedy transakce Ti musí být starší neţ transakce Tj. Pokud toto neplatí, transakce Ti je zrušena a provede se rollback. T1 T2 T3 T4 LOCK(A); READ(A) LOCK(B);WRITE(B) UNLOCK(A); UNLOCK(B) LOCK(A); Dies LOCK(A); Waits LOCK(A);WRITE(A) LOCK(B);READ(B) LOCK(C);WRITE(C) UNLOCK(B);UNLOCK(C) Obrázek 12: Prevence uváznutí s algoritmem wait-die Zdroj: upraveno podle (6) LOCK(A); Dies LOCK(A);LOCK(D) READ(D);WRITE(A) UNLOCK(A);UNLOCK(D) Typické pro wait-die algoritmus je, ţe starší transakce čeká, aţ mladší uvolní poţadovaný datový objekt, takţe čím je transakce starší, tím je více náchylná k čekání. Pokud je ve wait-die algoritmu transakce T i zrušena a je proveden rollback, protoţe poţadovala data drţená transakcí T j, můţe T i, pokud je restartována, opět spustit stejnou sekvenci operací a pokud jsou data stále uzamčena transakcí T j, dojde opět ke zrušení transakce T i a tato situace se můţe stále opakovat, dokud nedojde k uvolnění poţadovaných dat. 1.5.1.2 Wound-wait Je preemptivní technika, jedná se o opak algoritmu wait-die - pokud transakce T i poţaduje data drţená transakcí T j, T i bude čekat pouze v případě, ţe její časové razítko je vyšší neţ časové razítko T j (tedy T i je mladší neţ T j ), jinak je T j zrušena (v algoritmu se pouţívá termín wounded) a je proveden její rollback. Na rozdíl od algoritmu wait-die, v algoritmu wound-wait starší transakce nikdy nečeká na mladší. 18
T1 T2 T3 T4 LOCK(A); READ(A) LOCK(B);WRITE(B) UNLOCK(A); UNLOCK(B) LOCK(A); Waits LOCK(A); Waits LOCK(A);WRITE(A) LOCK(B);READ(B) Wounded Obrázek 13: Prevence uváznutí s algoritmem wound-wait Zdroj: upraveno podle (6) LOCK(A); Waits LOCK(A);LOCK(D) READ(D);WRITE(A) UNLOCK(A);UNLOCK(D) Pokud transakce T j poţaduje data drţená starší transakcí T i, je transakce T i zrušena a je proveden rollback. Pokud dojde k restartu transakce T i, ta nyní čeká na uvolnění dat transakcí T j, oproti algoritmu wait-die tedy můţe docházet k niţšímu výskytu operace rollback. 1.5.2 Timeout Řešení uváznutí pomocí časového limitu (timeout) je metodou na rozmezí mezi prevencí, kde k uváznutí nemůţe dojít, a detekcí. Funguje na jednoduchém principu, kdy je stanoven časový limit pro získání zámku a pokud se to transakci nepodaří, dojde ke zrušení transakce a je proveden rollback. Pokud tedy de facto jiţ došlo k uváznutí, po zrušení jedné z transakcí dojde k uvolnění a ostatní transakce mohou pokračovat. Tento algoritmus je poměrně jednoduchý na implementaci, ale je vhodný spíše pro systémy s kratšími transakcemi a je také citlivý na správné nastavení hodnoty čekání na získání zámku. 1.5.3 Detekce a zotavení Uváznutí se většinou týká pouze malého počtu transakcí, takţe můţe být výhodné, místo prevence, řešit tento problém aţ v případě, ţe k němu dojde. Aby to bylo moţné, musí databázový stroj periodicky monitorovat informace o zámcích, musí existovat algoritmus pro identifikaci uváznutí a systém musí provést zotavení z tohoto uváznutí. Pro popis a detekci uváznutí se pouţívá orientovaný graf, který se nazývá graf čekání (wait-for graph). 19
1.5.3.1 Wait-for graph Mnoţina uzlů tohoto grafu odpovídá mnoţině všech aktivních transakcí v systému, hranou je potom orientovaná dvojice T i T j, kde transakce T i čeká na uvolnění zámku drţeného transakcí T j. Manaţer zámků periodicky upravuje graf podle aktivních transakcí i jejich zámků (drţených i poţadovaných) a pokud v grafu vznikne cyklus, znamená to, ţe v systému došlo k uváznutí. Nejlépe si pouţití grafu čekání předvedeme na konkrétním příkladu - máme rozvrh čtveřice transakcí T1, T2, T3 a T4 (obrázek 9). T1 T2 T3 T4 1 LOCK(A) 2 LOCK(B) 3 LOCK(C) 4 LOCK(D) 5 LOCK(B) 6 LOCK(C) 7 LOCK(D) 8 LOCK(A) Obrázek 14: Rozvrh ilustrující vznik uváznutí Zdroj: autor Nejprve vytvoříme graf čekání po provedení kroku 7, tedy přesně před vznikem uváznutí (obrázek 15). T1 T2 T4 T3 Obrázek 15: Graf čekání před vznikem uváznutí Zdroj: autor Pokud vytvoříme graf po provedení kroku 8 (obrázek 16), vidíme, ţe přibyla hrana T4 T1 a došlo ke vzniku cyklu systém se tedy nyní nachází ve stavu uváznutí. 20
T1 T2 T4 T3 Obrázek 16: Graf čekání po vzniku uváznutí Zdroj: autor 1.5.3.2 Zotavení z uváznutí Pokud systém zjistí, ţe se nachází v uváznutí, musí se z této situace nějakým způsobem zotavit nejčastějším řešením potom bývá zrušení některé z transakcí zapojených v uváznutí. Prvním krokem je obvykle výběr vhodné oběti (victim selection) měla by se vybrat transakce, jejíţ zrušení bude mít nejniţší nároky na zdroje. Zde se můţe projevit mnoho faktorů, např. jak dlouho jiţ transakce běţí, s kolika datovými objekty pracuje, kolik jich bude potřebovat pro své dokončení, kolik transakcí ovlivní případný rollback apod. Příkladem můţe být např. patent US7185339 společnosti Oracle Victim selection for deadlock detection. Druhým krokem je samotné zrušení, rollback transakce. Nejjednodušším řešení je úplný rollback, tedy zrušení transakce a její restart. Efektivnější je zrušení pouze některých kroků transakce (partial rollback), ale to samozřejmě klade další nároky na systém - je nutné udrţovat dodatečné informace o stavu běţících transakcí (např. sekvence poţadavků a přidělení zámků) a samozřejmě musí být transakce po tomto částečném rollbacku schopna opět pokračovat. Problémem, který je nutné během zotavení řešit, je moţné stárnutí transakcí (starvation). K tomu můţe dojít, pokud je ke zrušení opakovaně vybrána stejná transakce, je tedy nutné zajistit, aby transakce mohla být vybrána pouze v konečném (a malém) počtu případů. 21
2 Metody používané pro řízení souběžného přístupu 2.1 Zámky Jedním ze způsobů, jak zajistit uspořádatelnost, je vyţadovat, aby přístup k datovým objektům (v případě databázového stroje jsou to většinou datové bloky nebo řádky v tabulce) byl exkluzivní, tedy pokud jedna transakce přistupuje k objektu, ţádná další ho nemůţe modifikovat. Nejobvyklejším způsobem, jak toto vynutit, je umoţnit transakci přistup k objektu pouze, pokud má v drţení zámek k tomuto objektu. Rozlišujeme dva základní módy uzamčení objektu sdílený, který pokud transakce obdrţí, můţe objekt číst, ale nemůţe ho modifikovat. Exkluzivní zámek potom umoţňuje operace čtení i zápisu. Tabulka 3 zobrazuje moţnou kompatibilitu zámků poţadovaných různými transakcemi nad jedním objektem - pokud si tuto informaci spojíme s informací, jaká operace vyţaduje jaký zámek, asi nás nepřekvapí, ţe kompatibilita operací odpovídá kompatibilitě z tabulky 1 v kapitole o uspořádatelnosti, zvláště pokud si uvědomíme, ţe cílem zámků je zajistit uspořádatelnost transakčního rozvrhu. sdílený exkluzivní sdílený ANO NE exkluzivní NE NE Tabulka 3: Matice kompatibility zámků Zdroj: (11) Pokud transakce T i chce přistupovat k objektu O, musí nejprve získat zámek nad objektem O, pokud objekt O je jiţ uzamčen v nekompatibilním módu jinou transakcí, musí transakce T i čekat, dokud nejsou všechny nekompatibilní zámky uvolněny. Vezměme dvě transakce T 1 ={READ(A), WRITE(A)}a T 2 ={READ(A)}, jejich rozvrh by s vyuţitím zámků mohl vypadat například jako na obrázku 17. 22
T 1 T 2 LOCK-S(A) READ(A) LOCK-S(A) READ(A) UNLOCK(A) LOCK-X(A) WRITE(A) UNLOCK(A) Obrázek 17: Rozvrh transakcí s využitím zamykání Zdroj: autor Je vidět, ţe transakce T 2 mohla získat sdílený zámek na objektu A, protoţe sdílené zámky jsou vzájemně kompatibilní, ale transakce T 1 jiţ musela čekat na získání exkluzivního zámku, dokud transakce T 2 svůj sdílený zámek neuvolnila. 2.1.1 Dvoufázové zamykání (2PL) Jedním z reálných protokolů zaloţených na zámcích a zajišťujících uspořádatelnost je tzn. dvoufázové zamykání (two-phase locking 2PL). Jedná se protokol široce rozšířený v komerčních databázových systémech, například v databázích Oracle, kde je vyuţíván ve spojení s multiverzováním (viz kapitoly 2.4 a 3). Základem 2PL protokolu jsou tři pravidla (1): 1. Pokud rozvrhovač obdrţí poţadavek na operaci, nejprve otestuje, jestli zámek potřebný pro tuto operaci není v konfliktu s jiţ existujícím zámkem. Pokud ano, rozvrhovač přinutí transakci čekat, dokud se nepodaří zámek přidělit. 2. Kdyţ transakce získá zámek, zámek nemůţe být uvolněn přinejmenším do doby, neţ byla dokončena operace vyţadující zámek. 3. Pokud byl jednou zámek uvolněn, nemůţe transakce jiţ transakce ţádný další zámek získat. Pravidlo tři je příčinou, proč se tento protokol nazývá dvoufázovým. Dělí totiţ transakci na dvě fáze fázi zamykání (growing phase) a fázi odemykání (shrinking phase). V první fázi, fázi zamykání, můţe transakce získávat zámky, ale ţádný zámek nemůţe uvolnit. Ve druhé fázi můţe transakce zámky jiţ pouze uvolňovat (5). Tato základní varianta 23
dvoufázového zamykání není odolná proti uváznutí, ani proti kaskádovému abortu a také nezajišťuje zotavitelnost rozvrhu, ale jak si ukáţeme dále, zajišťuje jeho uspořádatelnost. Předpokládejme, ţe existuje mnoţina transakcí T 0,T 1,,T n, které splňují pravidla 2PL a mají neuspořádatelný rozvrh. Neuspořádatelný rozvrh znamená, ţe musí existovat cyklický precedenční graf, předpokládejme tady, ţe existuje precedenční graf obsahující cyklus T 0 T 1 T n-1 T n T 0. Ať t i je čas, kdy transakce T i získala svůj poslední zámek (tento okamţik se nazývá lock point). Pro všechny transakce kde T i T j tedy platí t i <t j. Pro cyklus potom musí platit t 0 <t 1 <t 2 < <t n-1 <t n <t 0 a protoţe nemůţe platit t 0 <t 0, nemůţe ani nastat situace, kdy by pří dodrţeni 2PL v precedenčním grafu vznikl cyklus. Tímto lze tedy dokázat, ţe 2PL vţdy produkuje uspořádatelný rozvrh. Existují různé varianty 2PL, já se alespoň stručně zmíním o dvou z nich konzervativním 2PL (C2PL) a striktním 2PL (S2PL). C2PL se od základního 2PL liší v tom, ţe transakce musí získat všechny zámky, ještě předtím neţ začne. Tímto je zaručeno, ţe transakce, která jiţ získala zámky, nemůţe být blokována jinou transakcí. Toto chování sice v průměru zkracuje čas, po který transakce uzamyká objekt, ale na druhou stranu zase drţí více zámků, neţ v daný okamţik potřebuje. Tím, ţe C2PL musí získat všechny zámky v jednom kroku před začátkem transakce, je odolné vůči uváznutí a také zotavitelné a odolné proti kaskádovému abortu. Poměrně významným omezením C2PL je fakt, ţe pokud transakce nemůţe získat všechny zámky při svém úvodním poţadavku, nezíská ţádný a také to, ţe kaţdá transakce musí na svém počátku deklarovat všechny objekty, které bude číst nebo zapisovat (read a write set) a to není vţdy moţné pro tyto omezení se C2PL příliš nepouţívá. Většina implementací 2PL vyuţívá modifikaci S2PL - tato modifikace se od základního 2PL liší v tom, ţe uvolňuje všechny zámky najednou, při konci transakce (správněji řečeno, je v případě S2PL nutné do konce transakce drţet pouze exkluzivní zámky, varianta, která drţí všechny zámky, se někdy nazývá rigorous 2PL). Tato modifikace není odolná proti vzniku uváznutí, ale zajišťuje zotavitelnost rozvrhu a odolnost proti kaskádovému abortu. 24
2.1.2 Strukturované zamykání Zatím jsme uvaţovali zamykání pouze na úrovni určitého objektu, v některých případech pro nás ovšem můţe být výhodnější seskupit objekty do větších celků a umoţnit zamčení těchto celků, tím minimalizovat počet zámků a sníţit reţii spojenou s jejich správou. Pro tyto účely se pouţívá tzv. strukturované zamykání (Multiple granularity locking), pro ilustraci uvaţujme například strukturu jako na obrázku 18. DB A1 A2 F a F b F c r a1 r a2 r an Obrázek 18: Strukturované zamykání Zdroj: upraveno podle (11) Máme databázi, která se dělí na oblasti, kaţdá oblast obsahuje několik souborů, kaţdý soubor obsahuje záznamy. Kaţdý z uzlů je moţné zamknout individuálně, stejně jako v případě 2PL, budeme pouţívat sdílený a exkluzivní zámek. Pokud nyní transakce získá explicitně zámek na některém uzlu, všichni jeho potomci jsou implicitně také zamčeni a to ve stejném módu, jako tento uzel. Předpokládejme transakci T i, která uzamkla celý soubor F a, implicitně jsou také zamčeny všechny záznamy v tomto souboru. Následně chce transakce T j zamknout záznam r a2, ale tento záznam není explicitně uzamčen! Systém tedy, aby zjistil, jestli je moţné záznam zamknout, musí projít hierarchický strom od jeho kořene aţ po záznam r a2 a pokud je některý z uzlů uzamčen v nekompatibilním módu, T j musí čekat. Dále předpokládejme transakci T k, která chce uzamknout celou databázi jak ale systém pozná, ţe někde v celé struktuře není nekompatibilní zámek? Jednou z moţností je prohledat celý strom, tím se ale připravíme o výhodu tohoto konceptu. Lepší je zavést novou kategorii zámků intenční zámky (intention lock) (1). Pokud je uzel uzamčen v intenčním módu, znamená to, ţe některý z potomků tohoto uzlu je explicitně uzamčen intenční zámek je umístěn na všechny předchůdce uzlu dříve, 25
neţ je uzel explicitně uzamčen. Rozlišujeme tři nové druhy zámků: intenční sdílený (intention- shared) - explicitní zámky na niţší úrovni jsou pouze sdílené; intenční exkluzivní (intention-exclusive) některý z explicitních zámků na niţší úrovni je exkluzivní; sdílený a intenčně exkluzivní zámek (shared and intention-exclusive) - podstrom tohoto uzlu je explicitně uzamčen sdíleným zámkem a některý z objektů na niţší úrovni je zamčen exkluzivním zámkem. IS IX S SIX X IS Ano Ano Ano Ano Ne IX Ano Ano Ne Ne Ne S Ano Ne Ano Ne Ne SIX Ano Ne Ne Ne Ne X Ne Ne Ne Ne Ne Tabulka 4: kompatibilita zámků při strukturovaném zamykání Zdroj: (11) 2.2 Timestamp Ordering Tyto protokoly vyuţívají pro zajištění uspořádatelnosti techniku časových razítek (timestamp). Kaţdá transakce v okamţik svého vzniku obdrţí unikátní značku TS(T i ). Pokud transakce T i obdrţí timestamp TS(T i ) a transakce T j, která začne po transakci T i, obdrţí timestamp TS(T j ), potom platí, ţe TS(T i ) < TS(T j ). Pro získání časového razítka se obvykle vyuţívají systémové hodiny nebo čítač, který se s kaţdou transakcí navýší, respektive kombinace obou těchto metod. Základním pravidlem Timestamp Ordering protokolu je: pokud operace I i a I j jsou konfliktní, potom je operace I i provedena před I j pokud platí, ţe TS(T i ) < TS(T j ). Představme si precedenční graf rozvrhu S, pokud existuje hrana precedenčního grafu T i T j, tak musí existovat dvojice konfliktních operací I i a I j, kde I i < I j. Jinak řečeno, podle pravidla Timestamp Ordering protokolu platí TS(T i ) < TS(T j ). Pokud v grafu existuje cyklus T 0 T 1 T n-1 T n T 0, potom musí také platit TS(T 0 ) < TS(T 0 ) a to není moţné, takţe není moţný ani vznik cyklu v precedenčním grafu a je zřejmé, ţe tento protokol zaručuje uspořádatelnost. 26
Časové razítko není přiřazováno pouze transakcím, ale také pro kaţdý objekt je definována dvojice časových razítek: W-TS(O) obsahuje časovou značku poslední transakce, která provedla úspěšný zápis do objektu O. R-TS(O) obsahuje časovou značku poslední transakce, která provedla úspěšné čtení objektu O. Předpokládejme transakci T i, která chce provést operaci čtení na objektu O, vzhledem k moţným konfliktním operacím mohou nastat dvě situace: TS(T i ) < W-TS(O), to znamená, ţe transakce T i poţaduje hodnotu O, která jiţ byla přepsána, transakce bude tedy zrušena. TS(T i ) W-TS(O), transakce je novější neţ poslední modifikace objektu O, objekt O bude přečten a do R-TS(O) je přiřazena vyšší hodnota z R-TS(O) a TS(T i ). Předpokládejme, ţe transakce T i chce modifikovat (zapsat) objekt O, tedy vyvolat operaci WRITE(O): TS(T i ) < R-TS(O), novější transakce přečetla objekt O, nemůţeme tedy provést modifikaci, protoţe tím by se data přečtená novější transakcí stala neplatná. Transakce bude zrušena. TS(T i ) < W-TS(O), transakce T i se pokouší zapsat zastaralou hodnotu objektu O, dojde tedy ke zrušení transakce. V ostatních případech systém provede zápis a do W-TS(T i ) je vloţena hodnota TS(T i ). Jak jsme si jiţ dříve dokázali, Timestamp Ordering protokoly zajišťují uspořádatelnost, kromě toho také zajišťují odolnost proti uváznutí, protoţe v tomto protokolu nedochází k čekání. Na druhou stranu můţe docházet ke stárnutí transakcí (starvation), pokud sekvence krátkých konfliktních transakcí opakovaně způsobí restart dlouhé transakce. Protokol také můţe vygenerovat nezotavitelený rozvrh, ale dá se rozšířit tak, aby tomuto problému bránil to lze udělat například provedením všech operací WRITE najednou, aţ na konci transakce. 27
2.2.1 Thomas Write Rule Představme si, ţe rozvrhovač obdrţel operaci zápisu W i (O) transakce T i poté, co jiţ odeslal ke zpracování operaci zápisu W j (O) pocházející od transakce T j, ale zároveň platí TS(T i )<TS(T j ). Pravidla řazení podle časových razítek vyţadují, aby operace W i (O) byla odmítnuta, ale pokud se jedná o synchronizaci pouze operací zápisu, není toto zamítnutí nutné v Timestamp Ordering protokolech odpovídá sekvence zápisů podle pořadí časových razítek operaci zápisu s nejvyšším časovým razítkem, ostatní operace mohou být ignorovány. Toto zjištění vede k pravidlu, které se nazývá Thomas' Write Rule (Thomasovo pravidlo zápisu). Toto pravidlo říká: nechť transakce T j je transakcí s nejvyšším časovým razítkem, která zapsala do objektu O předtím, neţ rozvrhovač obdrţel poţadavek W i (O). Pokud platí TS(T i )>TS(T j ), provede se operace W i (O) a změní se časové razítko W-TS(O), jinak se operace W i (O) neprovede a dojde pouze ke změně časového razítka W-TS(O). 2.3 Multiversion concurrency control Cílem těchto protokolů je zajistit, aby transakce nikdy nečekala na operaci čtení (nebo dokonce nebyla zrušena). Princip je poměrně jednoduchý během operace zápisu vyniká vţdy nová verze objektu, kaţdý zápis Wi(O) vytvoří vlastní verzi objektu Oi, starší verze objektů nejsou mazány, ale jsou k dispozici pro operace čtení. Jak se tyto nové vlastnosti projeví na konfliktních operacích? Víme, ţe dvě operace jsou konfliktní, pokud kaţdá náleţí k jiné transakci, obě pracují se stejným objektem a alespoň jedna z nich je WRITE. Pro potřeby mutliverzovacího protokolu je nutné tuto definici pouze lehce upravit s tím, ţe aby došlo ke konfliktu, musí obě operace přistupovat ke stejné verzi datového objektu (1)(2). Jaké konflikty jsou tedy moţné podle této upravené definice? Vezměme první typ konfliktu, kdy operace čtení předchází operaci zápisu: Ri(Oj) < Wj(Oj), je zřejmé, ţe k tomuto konfliktu dojít nemůţe, protoţe verze objektu nad kterou by k němu mohlo dojít, v okamţiku čtení ještě ani neexistuje. Konflikt mezi dvěma operacemi WRITE, Wi(Oi) < Wj(Oi) není také moţná, protoţe kaţdý zápis vytváří vlastní verzi objektu. Konflikt je moţný pouze v posledním případu, kdy operace WRITE předchází operaci READ, Wi(Oi) < Rj(Oi). Aby bylo moţné dokázat, ţe rozvrh podle MVCC je uspořádatelný, je třeba rozšířit teorii uspořádatelnosti o další dva typy rozvrhu nebo historie: více-verzový rozvrh (MV) a 28
jedno-verzový rozvrh (1V), který je interpretací uţivatelského pohledu na MV rozvrh jako na jednu verzi. Sériový 1V rozvrh je to, co uţivatel vnímá jako správné, je tedy třeba dokázat, ţe kaţdý z MV rozvrhů, které rozvrhovač můţe vytvořit, je ekvivalentní sériovému 1V rozvrhu (1). Vyjděme z definice konfliktové ekvivalence, která říká, ţe MV rozvrh S MV je ekvivalentní 1V rozvrhu S 1V, pokud platí, ţe kaţdý pár konfliktních operací v S MV je ve stejném pořadí také v S 1V. Nyní vezměme jednoduchý rozvrh S MV =W 0 (x 0 ) C 0 W 1 (x 1 ) C 1 R 2 (x 0 ) W 2 (y 2 ) C 2 Vidíme, ţe jediné konfliktní operace jsou W 0 (x 0 ) a R 2 (x 0 ), nyní mapováním verzí objektu na korespondující objekt vytvoříme odpovídající 1V rozvrh S 1V =W 0 (x) C 0 W 1 (x) C 1 R 2 (x) W 2 (y) C 2 Je vidět, ţe jediné dvě konfliktní operace jsou ve stejném pořadí jako v MV rozvrhu, takţe podle definice konfliktové ekvivalence by rozvrhy mely být ekvivalentní. Kdyţ ale prozkoumáme, co čte transakce T 2 v MV a co v 1V rozvrhu, zjistíme, ţe je zde rozpor v MV rozvrhu čte T 2 po T 0, ale v 1V rozvrhu čte po T 1. Konfliktovou ekvivalenci tedy nelze pouţít, protoţe operace v MC a 1V jsou z hlediska konfliktů rozdílné. Protoţe tedy konfliktová ekvivalence nejde pouţít, pouţijeme ekvivalenci pohledovou. Ta říká, ţe dva rozvrhy S a S' jsou pohledově ekvivalentní, pokud platí: transakce T i čte první v S, tak musí číst první také v S', transakce T i čte z T j, tak musí stejně číst také v S' a pokud T i poslední zapisuje v S, musí poslední zapisovat také v S'. Pokud nyní porovnáme oba rozvrhy, vidíme, ţe v S MV čte T 2 z T 0, ale v S 1V čte z T 1, tedy rozvrhy nejsou pohledově ekvivalentní. Jako další krok je nutné dokázat, ţe kaţdý MV rozvrh je ekvivalentní sériovému 1V rozvrhu. Bohuţel, jak si ukáţeme na příkladu, nelze jednoduše pouţít precedenční graf, protoţe existují takové MV rozvrhy, pro které neexistuje ekvivalentní 1V rozvrh. Představme si například MV rozvrh S1 S1=W0(x0) C0 R1(x0) W1(x1) C1 R2(x0) C2 29
Pokud kaţdou verzi objektu x budeme brát jako nezávislou, dostaneme precedenční graf na obrázku 18. T2 T1 T3 Nyní vytvořme sériový 1V rozvrh S2: S2=W0(x) C0 R1(X) W1(x) C1 R2(x) C2 Obrázek 19: Precedenční graf MV rozvrhu Zdroj: autor Přestoţe S1 je sériový rozvrh a graf je acyklický, není rozvrh S1 pohledově ekvivalentní sériovému 1V rozvrhu S2, protoţe T2 čte v S1 z T0, ale v S2 čte z T1. Pouze podmnoţina sériových MV rozvrhů, která se označuje jako monoversion (v některé literatuře také jako 1-serial) rozvrhy, je ekvivalentní sériové 1V rozvrhu. Monoversion rozvrhem označujeme takový MV rozvrh, kde Ti čte x z Tj a Tj je nejnovější transakce před Ti, která zapsala nějakou verzi x. Všechny multiversion rozvrhy jsou ekvivalentní sériovým 1V rozvrhům, takţe abychom prokázali, ţe MVCC je korektní, musíme ukázat, ţe MV rozvrhy jsou ekvivalentní monoversion rozvrhům. Aby to bylo moţné, je třeba vyuţít tzv. multiversion precedenční graf a platí, ţe MV rozvrh je ekvivalentní monoversion rozvrhu, pokud jeho multiversion precedenční graf je acyklický 1. 1 Formální důkaz např. (1)(2)(12) 30
2.3.1 Multiversion timestamp ordering Jako v kaţdém protokolu pracujícím na principu řazení časových razítek, je kaţdé transakci T i před její exekucí přiřazen jedinečný timestamp TS(T i ). Kaţdý zápis datového objektu x vygeneruje novou verzi objektu x i a ke kaţdé verzi je přiřazeno časové razítko transakce, která ji vytvořila. Operace na datovém objektu rozvrhovač překládá do operací nad verzí tohoto objektu: při operaci R i (x) nalezne verzi x k objektu x, kde T k má nejvyšší timestamp TS(T k ) niţší neţ TS(T i ) a tuto verzi přečte. Operace čtení nemůţe nikdy čekat nebo být zrušena. W i (x), pokud existuje operace čtení R j (x k ), kde platí TS(T k ) <=TS(T i ) <=TS(T j ), je operace W i (x) zrušena a proveden restart transakce T i. V ostatních případech je proveden zápis nové verze, nebo v případě i=k je přepsána verze x k. Jak je vidět, operace zápisu můţe být zrušena. Verze, které jiţ nejsou potřeba, jsou odstraňovány - verze x k a x j jsou verze objektu x, pokud platí TS(T k ) < TS(T i ) a zároveň TS(T j ) < TS(T i ), kde TS(T i ) je timestamp nejstarší transakce v systému, potom starší z verzí x j a x k je smazána. Tato základní verze protokolu není zotavitelná, ani odolná proti kaskádovému rušení. Rozšíření, které by tomuto bránilo, je moţné podobně jako u Timestamp protokolu. 2.3.2 Multiversion 2PL V klasickém 2PL protokolu můţe exkluzivní (pro zápis) zámek na objektu x zabránit transakci v získání sdíleného (pro čtení) zámku. Multiversion 2PL tento konflikt řeší pouţitím více verzí x stejně jako pro ostatní MV protokoly platí, ţe operace zápisu vytvoří novou verzi objektu x a neblokuje tedy čtení. Kaţdá transakce dostane přiřazenu unikátní hodnotu transakčního čítače (jedná se vlastně opět o časové razítko), který se navyšuje s kaţdým potvrzením (commit) v databázi (příkladem tohoto čítače můţe být třeba SCN v databázích Oracle). Pokud transakce provádí operaci čtení, transakce získá sdílený zámek nad objektem a přečte verzi vytvořenou transakcí s hodnotou čítače nejvyšší niţší nebo rovnou čítači transakce, která čte. Pokud transakce provádí zápis, musí nejprve získat exkluzivní zámek, poté zapíše novou verzi, jejíţ čítač zatím nastaví na. Po vykonání všech akcí transakce dojde k jejímu potvrzení čítač se navýší o 1 a hodnota čítače se přiřadí verzi zapsané touto transakcí, dojde k uvolnění všech zámků. 31
Potvrzení můţe v daný okamţik provádět vţdy pouze jedna transakce. Mazání starých verzí probíhá obdobným způsobem jako v případě multiversion timestamp ordering protokolu. Popsaný protokol je zotavitelný a je odolný proti kaskádovému rušení, popsaná implementace je postavena na striktním 2PL, ale stejně jako striktní 2PL není odolná proti uváznutí. 2.4 Validation-based concurrency control V případech, kdy většina transakcí je read-only, můţe být počet konfliktů poměrně nízký a reţie spojená s řízením souběţného přístupu, jak jsme si ho ukázali v předchozích kapitolách zbytečně vysoká. V těchto případech tedy můţe být vhodné pouţít protokol, který je zaloţen na optimistickém přístupu, ţe ke konfliktu mezi transakcemi nedochází a případný konflikt řeší aţ na konci běhu transakce. V tomto protokolu kaţdá transakce proběhne ve třech fázích a v pořadí - čtení, validace a zápis: 1. Ve fáze čtení (read phase) jsou provedeny všechny operace transakce, data jsou přečtena z databáze a případné zápisy proběhnou do privátního pracovního prostoru. 2. V okamţiku potvrzení transakce (commit) nastává validační fáze. V jejím průběhu je otestováno, jestli by transakce mohla být v konfliktu s některou z paralelně běţících transakcí. Pokud je to moţné, transakce je zrušena, pracovní prostor je vyčištěn a transakce je restartována. 3. Pokud ve validační fázi není zjištěna moţnost konfliktu, nastává poslední fáze, zápis provedených změn. Data zapsaná do pracovního prostoru transakce jsou zkopírována do databáze. 32
Rozvrhovač pro validaci vyuţívá časová razítka transakcí a také informace o průběhu jednotlivých fází. Pro kaţdý pár transakcí T i a T j, kde TS(T i )<TS(T j ) potom musí platit alespoň jedna z trojice podmínek: 1. T i ukončila všechny tři fáze před začátkem T j. 2. T i byla dokončena dříve, neţ T j započala fázi zápisu a zároveň T i nezapisuje ţádná data, která čte T j. 3. T i dokončila svou fázi čtení dříve, neţ ji dokončila T j a T i nezapsala ţádná data, která čte nebo zapisuje T j. První podmínka znamená, ţe transakce proběhnou sériově. Druhá podmínka umoţňuje transakci T j číst data v době, kdy transakce T i ještě modifikovala objekty, ale nemůţe dojít ke konfliktu, protoţe T j nečte stejná data, jako T i modifikuje. T j můţe přepsat data zapsaná transakcí T i, všechny operace WRITE transakce T i proběhly před zápisy transakce T j. Třetí podmínka umoţňuje transakcím T i a T j zapisovat ve stejnou dobu, ale kaţdá z transakcí pracuje s rozdílnou mnoţinou objektů (mezi oběma mnoţinami není ţádný průnik). Validace těchto podmínek také vyţaduje, aby systém pro kaţdou transakci udrţoval seznam čtených a zapisovaných objektů. V průběhu validace také nemůţe dojít k potvrzení ţádné další transakce. Je zřejmé, ţe také existuje nějaká reţie spojená s tímto protokolem, ale pokud je v databázi malé mnoţství konfliktů, můţe tento přístup vést k lepší výkonosti, neţ u protokolů zaloţených na pesimistickém přístupu. Pokud je ovšem počet konfliktů vysoký, má neustálé restartování transakcí velmi negativní dopad na výkon databázového stroje. 33
3 Oracle RDBMS Relační databáze firmy Oracle je dnes nejrozšířenějším komerčním databázovým systémem a od verze 1 z roku 1978 dospěla aţ k aktuálně pouţívané verzi 11.2. 3.1 Implementace Concurrency Control v RDBMS Oracle Oracle pro zajištění konzistence a paralelního zpracování vyuţívá protokolu na základě dvoufázových zámků a udrţování více verzí (Multiversion 2PL) - Oracle podporuje konzistenci čtení jak na úrovni příkazu, tak na úrovni transakce (záleţí na pouţitém stupni izolace). Jako časové razítko (viz kapitola 2.3 Multiversion Concurrency Control) slouţí tzv. System Change Number (SCN). Zjednodušeně se jedná o číslo, které v čase definuje potvrzenou verzi databáze. Maximální hodnotou tohoto čísla je 2 48 a v okamţiku, kdy dosáhne maxima, dojde k vynulování. Toto má za následek vznik nové inkarnace databáze a s tím spojené následky jako zneplatnění předchozích záloh apod. V následujících kapitolách si ukáţeme, jaké zámky databáze vyuţívá, jak je implementováno MVCC a která omezení jsou s Oracle implementací spjaté. Začátkem transakce je v Oracle databázi vţdy začátek DML nebo DDL operace s výjimkou příkazu SELECT (pokud se nejedná o distribuovaný dotaz). Ukončení se v případu DML musí provést explicitně příkazem COMMIT nebo ROLLBACK, DML příkazy provedou potvrzení implicitně. Při sestavování transakcí je třeba si uvědomit, ţe dojde k potvrzení i provedením DDL operace a nakládat s nimi tedy opatrně. 3.1.1 Zámky Databáze Oracle pouţívá dvoufázové zamykání, v databázi jsou tedy dva základní módy zámků sdílené a exkluzivní, kde nad daným objektem můţe existovat více sdílených zámků, ale pouze jediný exkluzivní. Oracle umoţňuje zamykat data jak na úrovni celé tabulky, tak na úrovni řádku. Exkluzivní zámek brání sdílení zdrojů a je nutné ho získat, pokud transakce potřebuje modifikovat data, tedy pouze transakce, která tento zámek získá, můţe tato data měnit. Ostatní transakce musí čekat, dokud není zámek uvolněn. Sdílený zámek umoţňuje sdílení zdrojů, více transakcí můţe získat tento zámek nad jedním objektem. Přístup k datům 34