Umíme to s Delphi: 99. díl – jak zajišťuje databázový systém konzistenci a integritu?

Dnešní článek přináší pohled pod pokličku databázových systémů. Ptáte-li se, jakými způsoby pracují databázové systémy s transakcemi, jak zajišťují konzistenci dat a jakým způsobem se vyrovnávají s nastalými chybami, hledejte odpovědi právě zde.
Před týdnem jsme otevřeli databázovou problematiku konzistence, integrity a transakcí. V závěru článku jsme si slíbili, že dnes se podíváme trochu do zákulisí databázových systémů a vysvětlíme si, jakými způsoby databázový systém pracuje s transakcemi a jak zajišťuje, aby data byla v konzistentním stavu.

Ještě předtím je však nutné podotknout drobnou maličkost: principy popsané v následujících odstavcích se netýkají Delphi, ale platí obecně pro všechny transakční databázové systémy. Znalost a pochopení těchto principů není nutnou podmínkou úspěšného programování v Delphi, nicméně troufám si tvrdit, že může v mnoha případech usnadnit a zefektivnit práci. K zařazení následujících odstavců mě však vedou i další důvody – například fakt, že realizace transakcí je dobře pochopitelná a přitom atraktivní a zajímavá, a také vaše četné emaily a žádosti o popis i obecnějších (zejména databázových) témat.

Žurnál nejsou jen noviny

V této kapitole se nebudeme zabývat podrobnostmi a specifiky jednotlivých databázových platforem. Vysvětlíme si však některé obecné principy, které transakční databázové systémy používaly (a dosud používají) k zajištění práce s transakcemi.

Základním pojmem, který v souvislosti s transakcemi a se zachováváním integrity můžeme zaslechnout, je žurnál. Jedná se o jakýsi soubor (záznam, „log“), do něhož databázový systém zapisuje (ukládá) mnohé důležité informace o datech a prováděných transakcích. Implementace a konkrétní podoba žurnálu se liší systém od systému a není pro naše účely a pro pochopení základních principů vůbec podstatná.

Situace je tedy taková, že databázový systém zapisuje do souboru (do žurnálu) informace o změnách v datech. Ve své podstatě do něho tedy zapisuje probíhající transakce a jejich jednotlivé kroky. Obsahem žurnálu bývají údaje o každé provedené změně. O každé změně ukládá databázový systém do žurnálu zejména následující informace:

  • identifikace transakce (každá změna v transakčním databázovém systému se provede v rámci nějaké transakce. Pokud se vám zdá tato věta podivná, třeba proto, že racujete s transakčním databázovým systémem a nejste si vědomi, že byste třeba při zápisu do tabulky definovali a spouštěli nějaké transakce, pak vězte, že pokud explicitně nespecifikujete žádnou transakci, databázový systém provede požadovanou změnu v rámci jakési „default“ transakce (implicitní transakce), kterou si sám obhospodařuje. Každá změna je tedy součástí nějaké transakce a databázový systém uloží do žurnálu její identifikátor.).
  • identifikace objektu (tj. další vnitřní identifikátor označující objekt, který je předmětem změny. Pokud se například změna týká nějakého záznamu v tabulce ZAMESTNANCI, bude do žurnálu uvedena identifikace tabulky i záznamu. Tato položka žurnálu se samozřejmě později používá v případě, kdy je nutné zpětně zjistit, s jakými databázovými objekty bylo v průběhu transakce manipulováno).
  • uživatele (tj. identifikace uživatele, který je za změnu, resp. za transakci zodpovědný, kdo ji provedl a kdo tedy v důsledku změnil příslušná data).
  • čas provedení (tj. značka specifikující čas provedení příslušné změny. Tento údaj nemusí být nutně ve formátu „25.12.2002 v 11:57“, může se jednat třeba o obyčejnou rostoucí posloupnost celých čísel, nicméně „čas provedení“ – ať je již uložen jakkoliv – je pro správné fungování transakčního databázového systému a pro korektní práci s transakcemi naprosto klíčový, jak poznáme níže).
  • nová hodnota objektu (do žurnálu se vždy zapisuje nová hodnota, která byla objektu přidělena, například při změně řádku v tabulce se uloží údaje, které do příslušného řádku ukládáme. Důvod poznáme vzápětí, prozatím pouze naznačme, že příkaz „přejmenuj v tabulce ZAMESTNANCI zaměstnance PEPA na FRANTA“ nemusí vždycky způsobit, že databázový systém okamžitě provede fyzické přejmenování a Pepu navždy vypustí. Databázový systém se někdy tváří, jako že změnu provedl, ale ve skutečnosti ponechá v tabulce Pepu a Frantu zapíše jen do žurnálu. To vše si ale vysvětlíme později).

Výše uvedené informace se zpravidla ukládají do žurnálu za jakýchkoliv podmínek. V závislosti na způsobu transakčního zpracování se však někdy do žurnálu ukládá ještě jeden údaj:

  • stará hodnota objektu. Důvod poznáme za okamžik, ale někdy je z principu práce systému vhodné, aby uchovával i staré hodnoty objektů, které jsou předmětem změn. Pokud by například databázový systém zareagoval na požadavek „přejmenuj v tabulce ZAMESTNANCI zaměstnance PEPA na FRANTA“ tak, že okamžitě (fyzicky) přejmenuje Pepu na Frantu, je nanejvýš vhodné (a vlastně nutné), aby do žurnálu uložil původní hodnotu objektu, tj. Pepu pro případ, že by bylo nutné transakci zrušit (a obnovit původní hodnoty datových objektů).

    Jedna zepředu, druhá zezadu

    V minulém odstavci jsme si vysvětlili, jaké informace ukládá databázový systém do žurnálu. Na tomto místě si zkuste sami odpovědět na otázku, v jakých případech (kdy) vlastně žurnál potřebujeme? Kdy je databázovým systémem použit? Původní, nejčastější a základní použití spočívá v obnovách při chybě. Zde leží podstat používání žurnálu: jakmile dojde v průběhu práce systému k jakékoliv chybě nebo jakmile uživatel zruší probíhající transakci, je nutné uvést databázi do konzistentního stavu, tj. uvést data do takové podoby, v níž reprezentují nějakou reálnou, možnou, smysluplnou informaci o modelovaném světě.

    Nyní se zaměřme na chybové stavy. Dojde-li v běhu systému k jakékoliv významné chybě, která by mohla ovlivnit konzistenci dat, znemožnit korektní provoz a znemožnit provádění další práce, dostane se ke slovu žurnál. Nyní poznáme, jakým způsobem lze (principielně) se žurnálem pracovat. V zásadě existují dva základní způsoby (podotkněme, že následující dva způsoby se nijak netýkají vlastní obnovy chyb, ale popisují pouze práci se žurnálem. Jejich praktické použití si vysvětlíme za okamžik):

  • dopředné použití (tzv. REDO). V tomto případě se žurnál čte směrem dopředu, od starších záznamů k novějším. Po chybě se vychází z posledního kontrolního bodu (kontrolním bodem rozumějme okamžik, v němž je databáze v konzistentním stavu – jsou samozřejmě k dispozici všechna data z kontrolního bodu) a provedou se postupně všechny operace uložené v žurnálu. Žurnál se tedy čte od doby posledního kontrolního bodu do okamžiku vzniku chyby.
  • zpětné použití (tzv. UNDO, také označováno ROLLBACK). Při zpětném čtení se postupuje opačně: vychází se ze současného, tedy chybového a nekonzistentního stavu databáze. V tomto případě je však nutné, aby v žurnálu byly uloženy i staré hodnoty datových objektů. Postupným zpětným čtením žurnálu se obnovují původní hodnoty až do předchozího konzistentního stavu.

Znovu bych rád podotkl, že uvedené dvě metody čtení žurnálu samy o sobě nedokážou zajistit konzistenci ani nic neříkají o to, jak databázový systém postupuje v případě vzniku chyby. Jedná se jen a jen o popis možných způsobů, jak naložit se žurnálem.

Databázové systémy používají pro zajištění obnovy po chybě jeden (nebo oba) z uvedených způsobů v rámci tzv. metod zajištění konzistence dat. Těchto metod však existuje celá řada: některé jsou více, jiné méně sofistikované, moderních, účinné nebo jednoduché. V následujících odstavcích poznáme dvě nejklasičtější (a zároveň nejjednodušší) metody, které však v principu fungují a ukazují cesty, jakými se databázový systém může při kritických momentech ubírat.

Potvrzujeme na dvě fáze

První metoda, kterou si popíšeme, se nazývá dvoufázové potvrzování. Pro její pochopení je pouze nutné, abyste znali možné stavy transakcí – viz předchozí díl seriálu.

Metoda dvoufázového potvrzování vychází ze dvou jednoduchých pravidel:

  • Transakce nesmí provádět žádné fyzické změny v databázi, dokud není částečně potvrzená (tedy dokud se nedostane do stavu PC z obrázku z minulého dílu).
  • Transakce nesmí být částečně potvrzená, pokud není vytvořen příslušný záznam v žurnálu.

Toť vše, toto je celá metoda dvoufázového potvrzování. Pojďme si ji trochu rozebrat: poznáme totiž, že při dodržení těchto dvou pravidel nemůže dojít k nekonzistenci.

Transakce při svém běhu sice provádí všechny požadované změny, nicméně nezapisuje je fyzicky do databáze. Při částečném potvrzení transakce se zapíší všechny změny, ale jen do žurnálu, nikoliv do databáze. Databáze stále obsahuje původní hodnoty objektů. V okamžiku, kdy uživatel usoudí, že jsou všechny změny dokončené, dojde k potvrzení transakce. V tomto okamžiku teprve databázový systém začne procházet žurnál dopředným čtením (REDO) a zapisuje fyzicky do databáze data uvedená v žurnálu.

Dojde-li k jakékoliv chybě před potvrzením transakce, nic se neděje, protože databáze obsahuje konzistentní (původní) data. Dojde-li k chybě naopak až po potvrzení, také se nic neděje, protože veškerá nová data (vedoucí k novému konzistentnímu stavu) jsou uložena v žurnálu. Ani v jednom případě se tedy nedostaneme do situace, v níž máme nekonzistentní data a nevíme, jak je uvést do konzistentní podoby.

Metoda dvoufázového potvrzování má však velkou nevýhodu: při průběhu transakce musí být všechny zúčastněné objekty zamknuté a nikdo (tj. žádné jiné transakce) k nim nesmí mít přístup. V opačném případě by v datech vznikl ohromný nepořádek, který by již těžko šel uvést do konzistentního stavu. Jedna transakce by totiž změnila žurnál a tvářila se, že změnila původní data. Druhá by tato původní data vzala v domnění, že jsou již aktuální a chyba by byla na světě.

Kdybychom se však rozhodli splnit tuto nutnou podmínku (tedy podmínku „s objektem smí manipulovat v jednom okamžiku jen jedna transakce“), stala by se databáze prakticky nepoužitelnou. V rozsáhlejších databázích běží každým okamžikem třeba několik set (resp. tisíc) transakcí a kdyby mohla vždy pracovat jen jedna z nich, databáze by byla k ničemu.

Metoda dvoufázového potvrzování tedy ukazuje základní způsob ochrany před chybami, nicméně nedokáže uspokojivě vyřešit paralelní zpracování transakcí – což je otázka, kterou moderní databázové systémy považují za zásadní.

Pro paralelní běh transakcí je trochu použitelnější druhá metoda, kterou si popíšeme v následujícím odstavci.

Metoda přímého zápisu do databáze

Předchozí metoda dvoufázového potvrzování zapisovala změněná data jen do žurnálu, nikoliv do databáze. Změny fyzicky promítla až poté, co došlo k jejímu potvrzení. Naopak metoda přímého zápisu do báze dat zapisuje změny rovnou do databáze: změny v databázi se provedou ihned v době potřeby, tedy ještě předtím, než došlo k potvrzení transakce. V tomto případě ovšem existuje reálné nebezpečí, že bude nutné změny zrušit a databázi uvést do původního stavu: transakce může být zrušena, případně může dojít k chybě. Z toho důvodu je nutné ukládat do žurnálu původní hodnoty objektů. V případě chyby (nebo zrušení) se provede ROLLBACK a zpětným čtením žurnálu dojde k obnovení původních dat.

Jak bylo naznačeno výše, tento způsob práce s databází umožňuje lépe paralelní běh transakcí, protože objekty je možné používat hned (není nutné čekat na potvrzení transakce). Z toho důvodu je ale zákonitě obtížnější obnova po chybě nebo po zrušení transakce A: je nutné uvést do původního stavu nejen objekty dotčené transakcí A, ale i všemi dalšími transakcemi spuštěnými po transakci A, které objekt též použily. Dochází tedy ke kaskádovému rušení.

V této nejjednodušší podobě bychom ovšem nedokázali metodou přímého zápisu pokrýt všechny chyby, k nimž může dojít (např. chybu na pevném disku). Používá se proto kombinace zpětného čtení (ROLLBACK) a dopředného čtení (REDO), nicméně již není možné hovořit o „čistokrevné“ metodě přímého zápisu.

Strategie přímého zápisu do databáze v praxi

Abychom si v tomto dílu ukázali také nějaký praktický problém, vytvoříme modelovou situaci, kterou si můžete prohlédnout na následujícím obrázku:

Obrázek ukazuje časový průběh transakcí 1 až 5 a dále naznačuje dva důležité časové okamžiky: kontrolní bod (v němž došlo k zálohování všech dat) a chybu.

Co je nutné učinit v okamžiku chyby? Nejproblémovější budou zřejmě transakce 3 a 5, neboť ty do doby chyby neskončily a obsahují tedy nekonzistentní data – je nutné uvést databázi do stavu „před jejich spuštěním“.Na druhou stranu bychom uvítali, kdyby změny provedené transakcemi 2 a 4 z databáze nezmizely: obě transakce úspěšně dokončily svou činnost dostatečně dlouho před chybou, takže není důvod rušit je a uvádět databázi do stavu před nimi.

Uvedeme stručný algoritmus, který oba naznačené úkoly uspokojivě provede. Algoritmus není vůbec obtížný, k jeho pochopení je nutná jen trocha soustředění:

  • Vytvoříme dva seznamy: REDO, UNDO. Do seznamu UNDO dáme označení všech transankcí z kontrolního bodu (v našem případě tedy 2 a 3).
  • Pak začneme procházet žurnál dopředně. Narazíme-li na začátek transakce, zapíšeme ji do seznamu UNDO. Narazíme-li naopak na konec transakce, přesuneme ji ze seznamu UNDO do seznamu REDO. V našem případě tedy nejprve dáme do UNDO transakci 4, pak přesuneme do seznamu REDO transakci 2, pak přesuneme do REDO transakci 4 a nakonec dáme do UNDO transakci 5.
  • V tomto okamžiku je možné přejít k fázi zotavení. V jejím rámci nejprve zpětným čtením žurnálu uvedeme do původního stavu transakce ze seznamu UNDO (v našem případě tedy 3 a 5).
  • Nakonec zpracujeme dopředným čtením transakce z REDO (v našem příkladu transakce 2 a 4).

Další transakční témata

O transakcích bychom mohli hovořit ještě několik dalších dílů. Přestože jsme se o nich dozvěděli již spoustu informací, k jednomu z nejdůležitějších „transakčních“ témat jsme se dostali jen velmi okrajově: jedná se o paralelní zpracování transakcí, tedy o odpovědi na otázku „Jak zajistí databázový systém, aby mohlo víc transakcí probíhat v jeden okamžik a neměnily si data „pod rukama“?. Toto téma je ovšem poměrně rozsáhlé a pro potřeby našeho seriálu zbytečně pokročilé. Naznačíme proto pouze, že řešení paralelního zpracování transakcí může být například pomocí tzv. uzamykacích protokolů nebo pomocí tzv. časových značek.

Na závěr

V předchozích odstavcích jste – jak alespoň doufám – nalezli mnohé informace týkající se zákulisí databázových systémů a způsobů jejich práce. Věřím, že přestože dnešní článek se příliš netýkal Delphi, informace z něho shledáte alespoň trochu užitečnými.

V příštím díle budete trochu slavit: znovu se dočkáte svého oblíbeného hypertextového obsahu předcházejících padesáti částí.

Váš názor Další článek: RIAA měkne, budou amnestie pro uživatele výměnných sítí

Témata článku: , , , , , , , , , , , , , , , , , , , , , , , , ,