Programozói karrieretek során nagyon hamar fogtok találkozni adatbázisokkal, hiszen a legtöbb webszerver perzisztens adatokat is kezel. Egészen addig amíg egy időben csak egy nyitott kapcsolatot engedünk meg az adatbázissal az adatok módosítása, törlése vagy éppen új adatok létrehozása egyszerű. Ez viszont nagy felhasználóbázis mellett hatalmas várakozási időket eredményezhet, muszáj megengedi az egy időben több nyitott kapcsolatot az alkalmazásszerver és az adatbázis között. Ilyenkor viszont előfordulhat hogy egy időben próbálnak beszúrni vagy törölni adatokat egy táblából. Egyes komplex feladatoknál előfordulhat az is, hogy egy művelettel egymással kapcsolatban lévő táblákba szeretnénk adatokat lementeni. Tegyük fel, hogy egy új felhasználó regisztrál az alkalmazásunkban. Beérkezik a regisztrációs request amit elkezdünk feldolgozni, a fő ’CUSTOMER’ táblába lementjük a felhasználó alap adatait, viszont a címének csináltunk egy külön táblát, a ’CUSTOMER_ADDRESS’-t. Ilyenkor a request objektumból megkonstruáljuk a táblákba való entitásokat. Viszont hálózati problémák miatt megszakad a kapcsolat az adatbázissal miután lementettük a ’CUSTOMER’ adatokat, de még azelőtt hogy lementettük volna a ’CUSTOMER_ADDRESS’ táblába a címét. Ilyenkor nem hagyhatjuk benne a fő táblában a felhasználó adatait hiszen mikor következőre rányom a regisztráció gombra a rendszer úgy fogja érzékelni hogy létezik már az adatbázisban, akkor is ha az adatainak csak egy részét sikerült lementeni.
Többek között ezekre a problémákra kínálnak megoldást a tranzakciók. Tranzakció kezelést a legtöbb adatbázis szolgáltat, viszont vannak olyan keretrendszerek melyek saját implementációival backend szinten kezelhetjük ezeket a tranzakciókat. A tranzakcióknak 4 főbb tulajdonsága van ami miatt hasznosak, ezeket az ACID mozaikszóval rövidítjük:
- Atomicity
- Atomiság, vagy oszthatatlanság. Ez azt jelenti, hogy bármely tranzakciós tartományon belül vagy minden művelet sikeresen végrehajtódik vagy egyik sem hajtódik végre. Ezt a tulajdonságot naplózással biztosítják a tranzakciókezelők. A tranzakció naplózásnak több fajtája van, mindegyiknek megvannak az előnyei illetve a hátrányai, viszont közös tulajdonságuk, hogy ha bárhol megakad valamilyen művelet a tranzakción belül akkor az adatok mentéséről, módosításáról írt napló alapján visszafelé el tudja végezni ugyanazokat a műveleteket a kezelő és így meg nem történtté tenni a tranzakció hatásait. Vegyük példának az előbb említett felhasználó regisztrációt. Ott a ’CUSTOMER’ táblába írás előtt egy naplóba belekerül minden olyan metaadat ami ahhoz kell, hogy esetleges hiba esetén ezt a sort ki is tudja törölni a kezelő. Ezt a műveletet manuálisan is elindíthatjuk egy ’ROLLBACK’ parancs kiadásával, illetve le is zárhatjuk és a módosításokat permanensé tehetjük egy ’COMMIT’ utasítással.
- Consistency
- Konzisztencia. Ez a tulajdonság biztosítja, hogy amennyiben a tranzakció megnyitása pillanatában az adatbázis konzisztens állapotban volt, akkor a tranzakció után is abban lesz. Ez részletesebben kifejtve azt jelenti, hogy a tranzakció végén nem létezhet olyan adat az adatbázisban ami nem felel meg valamely, az adatbázis által definiált szabálynak. Pár példa: Nem létezhet olyan adat amire egy definiált constraint nem teljesül, nem létezhetnek duplikált adatok egyedi oszlopokban. Nem mutathat más táblában idegen kulcs egy a tranzakció által törölni kívánt fő kulcsra.
- Isolation
- Izoláció. Biztosítja, hogy az egy időben futó tranzakciók olyan állapothoz vezessenek el, mintha a tranzakciók egymás után futottak volna le. Nem fordulhat elő olyan, hogy egy végrehajtás alatt álló tranzakció hatásai láthatóak másik futó tranzakcióból. Itt érdemes megjegyezni, hogy a tranzakciós határokat érdemes minimalizálni. Az általunk írt kódban arra kell törekendi, hogy minél kevesebb időt töltsenek tranzakcióban, ne végezzenek költséges műveleteket és csak az maradjon tranzakciós határon belül aminél mindenképpen igénybe szeretnénk venni a tranzakciók tulajdonságait. Egy gyakori hiba többszálú rendszereknél a write-write conflict. Vagyis amikor több egyszerre futó tranzakció szeretné módosítani ugyanazt a rekordot. Ilyenkor a hiba akkor keletkezik ha a tranzakciók ugyanazokat a rekordokat módosították, viszont az egyik elbukott. Ilyenkor a jól lefutó tranzakciót sem tudjuk jóváhagyni mert a bukott tranzakció elveszett módosítása miatt nem tudjuk garantálni hogy konzisztens marad az adatbázis az új adatokkal. Ennek a problémának a minimalizálására két népszerű metodika létezik. Mindkettő vezet be újabb problémákat, de sokkal kevesebbszer fordulnak elő mint az alap write-write conflict.
Optimistic lock: A kettő közül talán a népszerűbb. Ilyenkor a tranzakció indulását követően akárhányszor olvasunk egy rekordot lejegyezzük annak például a verziószámát (ez lehet egy egyszerű inkrementáló mező, melyhez akárhányszor frissítjük a rekordot hozzáadunk 1-et). Amikor ezt a rekordot vissza szeretnénk írni az adatbázisba ismét ellenőrizzük a verziószámát és csak akkor írjuk vissza, ha az egyezik az elején kiolvasott számmal. Abban az esetben, ha a verziószám változik feltételezzük hogy a rekordot módosította másik tranzakció ezért az éppen futó tranzakciónkat ’ROLLBACK’-eljük. Esetleg hibát is dobunk, majd alkalmat adunk a felhasználónak hogy újra elindítsa a tranzakciót. Ezt olyan körülmények között érdemes használni ahol az adatok mivoltából arra tudunk következtetni, hogy nem fog gyakran ugyanaz az adat módosulni, például felhasználók személyes adatai. Hiszen ebben a metodikában a második tranzakciónk akkor is hibára fut és visszagördül, ha egy sikeresen végbemenő tranzakció módosította előtte az adott rekordot.
Pessimistic lock: Ebben az esetben amikor hozzáférünk egy rekordhoz azt zároljuk az egyedi használatunkra, ilyenkor ha fut egyéb tranzakció ami ugyanazt a rekordot szeretné módosítani amit zároltunk meg kell várnia míg az éppen futó tranzakció végbemegy és elengedi a rekordot. Ezt a metodikát választva figyelnünk kell arra, hogy a programunkat úgy alakítsuk ki, hogy a lezárt rekordok mindenképpen felszabaduljanak, hiszen ha engedünk el megfelelően egy lezárt rekordot az arra váró egyéb tranzakciók nem fogják tudni az általuk eszközölt módosításokat elvégezni.
- Izoláció. Biztosítja, hogy az egy időben futó tranzakciók olyan állapothoz vezessenek el, mintha a tranzakciók egymás után futottak volna le. Nem fordulhat elő olyan, hogy egy végrehajtás alatt álló tranzakció hatásai láthatóak másik futó tranzakcióból. Itt érdemes megjegyezni, hogy a tranzakciós határokat érdemes minimalizálni. Az általunk írt kódban arra kell törekendi, hogy minél kevesebb időt töltsenek tranzakcióban, ne végezzenek költséges műveleteket és csak az maradjon tranzakciós határon belül aminél mindenképpen igénybe szeretnénk venni a tranzakciók tulajdonságait. Egy gyakori hiba többszálú rendszereknél a write-write conflict. Vagyis amikor több egyszerre futó tranzakció szeretné módosítani ugyanazt a rekordot. Ilyenkor a hiba akkor keletkezik ha a tranzakciók ugyanazokat a rekordokat módosították, viszont az egyik elbukott. Ilyenkor a jól lefutó tranzakciót sem tudjuk jóváhagyni mert a bukott tranzakció elveszett módosítása miatt nem tudjuk garantálni hogy konzisztens marad az adatbázis az új adatokkal. Ennek a problémának a minimalizálására két népszerű metodika létezik. Mindkettő vezet be újabb problémákat, de sokkal kevesebbszer fordulnak elő mint az alap write-write conflict.
- Durability
- Tartósság. Ez a tulajdonság inkább vonatkozik az adatok fizikai tárolójára. A végrehajtott tranzakciók adatai nem veszhetnek el. Ezért olyan adattárolón kell tárolni őket mely megtartja őket szoftver/hardver hiba vagy áramszünet esetén is. Persze szoftver oldalon is van jelentősége, ennek értelmében a tranzakció tényleges lezárásának nem elégséges feltétele a módosítások végrehajtása memóriában például. A tranzakció és ezzel együtt a napló csak akkor zárható le, hogyha a módosított adatot sikeresen ki is tudtuk írni az adattárolóra.
Vituska Márk