Kolize při ukládání do databáze (transakce, zamykání, object version)
Za určitých okolností může docházet k tzv. kolizím při ukládání dat. Kolize mohou mít celou řadu příčin. Např. pokud se dva uživatelé současně pokusí opravit týž záznam, nebo pokud se uživatel pokusí opravit záznam v neobčerstveném číselníku či neobčerstvené dokladové agendě, přičemž příslušný záznam byl mezitím opraven např. z agendy otevřené v jiném okně aplikace či jiným uživatelem v síti apod. Různé typy kolizí se řeší různě. Zde se pokusíme objasnit, jak může ke kolizím dojít a jakým způsobem je systém řeší.
Přístup k problematice řešení kolizí při ukládání dat prošel v systému ABRA Gen určitým historickým vývojem.
V síťové verzi systému ABRA Gold (předchůdce ABRA Gen) se používala technologie zamykání záznamů, která vzniku kolizí zabraňovala - každý záznam mohl v jednom okamžiku měnit pouze jeden uživatel. Na druhou stranu byl tento přístup pro uživatele poměrně omezující - po celou dobu opravy jedním uživatelem byl záznam pro ostatní uživatele uzamčen a museli čekat na jeho uvolnění. Tento přístup se označuje jako pesimistický. Objasníme na jednoduchém příkladu:
Příklad 1: Mějme uživatele Nováka a Bláhu, pracující v síťové instalaci ABRA Gold. Dále nadefinované středisko 100 s názvem Obchod a ulicí Dlouhá 50. Uživatel Novák chce provést opravu střediska 100, a to jeho názvu, a uživatel Bláha chce z nějakého důvodu provést opravu téhož střediska 100, a to jména ulice. Novák zahájí opravu střediska 100. V ten moment dojde k zamčení záznamu střediska v databázi. Pokud Bláha chce zahájit opravu, systém oznámí, že to nelze, jelikož záznam je uzamčen a opravu nepovolí. Bláha musí počkat, až Novák svou opravu ukončí a záznam uvolní.
V systému ABRA Gx byl zaveden jiný přístup, který byl později převzat i do ABRA Gen. Jak bylo uvedeno v kapitole o technologii zpracování dat, jedná se o databázový klient/server systém, pracující na principu tzv. transakcí:
- je zahájena nějaká transakce s cílem provést požadovanou akci, např. uložit změny v záznamu (start transakce)
- následně transakce probíhá
- pokud se celou transakci podaří provést, je úspěšně ukončena, neboli potvrzena (commit transakce)
- pokud se celou transakci provést nepodaří (dojde k nějaké kolizi, např. nelze uložit data daného záznamu, jelikož byl změněn současně v rámci jiné transakce, či je blokován jinou transakcí), není transakce potvrzena a vše se vrátí zpět do stavu před jejím zahájením (rollback transakce)
V takovýchto systémech se většinou nepoužívá zahajování transakcí ihned při zahájení manipulace se záznamem, např. při vyvolání funkce Opravit (protože oprava záznamu by teoreticky mohla trvat i velmi dlouho, transakce by musela běžet po celou dobu provádění změn a podpora dlouho trvajících transakcí není u SQL databází obvyklá).
V ABRA Gen se tedy transakce ve většině případů zahajuje až v okamžiku ukládání. Uživatelsky to např. znamená, že je možné zahájit stejnou operaci se stejným záznamem a teprve při ukládání se kontrolují a řeší případné kolize. Tato technologie sice vzniku kolizí nepředchází, ale na druhou stranu uživatele nijak neomezuje, a proto se někdy označuje jako optimistický přístup. Vzniklou kolizi objasníme na stejném příkladu:
Příklad 2: Uživatel Novák i Bláha zahájí opravu střediska 100, Novák změní název střediska na Obchod celkem (ulici nemění) a Bláha změní ulici na Jirskou 475 (název střediska nemění). Novák záznam uloží. Akce uložení vyvolá zahájení a úspěšné potvrzení příslušné transakce, stav v databázi tedy je "středisko 100, Obchod celkem, Dlouhá 50". Poté chce uložit své změny Bláha. Zahájí se transakce. Systém ale zjistí, že záznam střediska byl (mezitím, co Bláha opravoval ulici) změněn a nahlásí kolizi (nemůže sám o sobě rozhodnout, jak pokračovat, zda ponechat změnu názvu provedenou Novákem, anebo data, která má Bláha, tj. původní název a novou ulici. Systém transakci zruší, akce uložení se neprovede.
Řešení takových kolizí je různé a závisí na jejich konkrétním typu.
Simulace pesimistického zamykání:
K situacím, kdy dva uživatelé začali v jeden okamžik opravovat tentýž záznam, docházelo v některých provozech často. Při výše popsaném optimistickém přístupu nastávaly kolize, tedy jeden z uživatelů nemohl své údaje uložit, o provedené změny přišel a musel je zadat znovu (podrobněji viz dále Kolize při změně ObjectVersion). To bylo nepříjemné zejména v případech déletrvajících editací rozsáhlých záznamů (např. několikařádkových dokladů). Aby se mohlo v těchto případech kolizím předcházet, byla do systému přidána možnost simulace pesimistického zamykání.
Simulace pesimistického zamykání se aktivuje pomocí parametru PessimisticEditLock, který musí být uveden v souboru nexus.cfg v sekci [
Příklad hlášení při pokusu o souběžnou editaci téhož záznamu s aktivovanou simulací pesimistického zamykání.
Každý z uvedených způsobů má své výhody i nevýhody. V současné době je využívána kombinace různých přístupů ve snaze maximalizovat efektivitu práce při souběžném používání systému větším množstvím uživatelů, konkrétně:
- na úrovni business objektů byl zachován optimistický přístup
- na úrovni uživatelského rozhraní je používána simulace pesimistického zamykání
Oproti dosavadnímu stavu byla nicméně zavedena celá řada vylepšení a změn. V současné době platí následující:
-
Simulace pesimistického zamykání je stále záležitostí uživatelského rozhraní systému (nejedná se o vlastnost business objektů), nicméně je možné s ní pracovat i ve skriptování a Web API.
Viz související FAQ Jak pomocí skriptování pracovat se zamykáním záznamů?
- Vytváření zámků je vázáno pouze na používání funkce Opravit v dokladových a číselníkových agendách. Není tedy implementováno v agendách jako jsou Firemní údaje, Nastavení parametrů mezd, Nastavení kompletace, Definice klíčů, Nastavení gastrovýroby apod.
-
Nejsou kontrolovány ani změny záznamů prováděné jiným způsobem, např.:
- Aktivity - funkce Předat
- Zakázky, Obchodní případy, Projekty - funkce Uzavřít / Zrušit uzavření
- Odeslaná pošta (PO) - funkce Uzávěrka
- apod.
-
Ke konfliktům může stále docházet při ukládání následných dokladů. Možné příčiny a vysvětlení:
- Při použití funkce Vytvořit se nevytvoří žádný nový zámek.
- Na nově vytvářeném dokladu se zámek nevytvoří proto, že se jedná o nový záznam a nikoli o opravu. Toto ničemu nevadí, protože ostatní uživatelé vytvářený následný doklad nevidí, dokud ho uživatel neuloží.
- Zámek se ale nevytvoří ani na zdrojovém dokladu, protože vytváření zámků je vázáno pouze na funkci Opravit (nikoli Vytvořit). Toto vadit potenciálně může, protože úspěšné uložení následného dokladu je podmíněno vytvořením vazby, kterou je zapotřebí zapsat do zdrojového dokladu. Pokud zdrojový doklad začne souběžně editovat někdo další, dojde později ke konfliktu. O existenci konfliktu se však uživatel dozví až při pokusu o uložení, nikoli při zahájení vytváření následného dokladu.
- Nekontroluje se existence případného zámku na zdrojovém dokladu při používání funkcí Vytvořit nebo Opravit.
- Při použití funkce Vytvořit se neprovádí kontrola na existenci zámku na zdrojovém dokladu, tj. neověřuje se, zda bude možné při ukládání následného dokladu vytvořit mezi následným a zdrojovým dokladem vazbu (bez které není uložení následného dokladu možné). Uživatel vytvářející následný doklad se o existenci konfliktu opět dozví až při pokusu o uložení.
- Kontrola se neprovádí ani při použití funkce Opravit na následném dokladu, který byl ze zdrojového vytvořen v minulosti - zámky a kontroly jsou pouze jednoúrovňové. Pokud byl například z objednávky přijaté vytvořený dodací list a uživatel A začne dodací list opravovat, automaticky vytvořený zámek zamezí pouze zahájení souběžné editace tohoto dodacího listu, možnost editace zdrojové objednávky nijak neomezuje. Pokud tedy mezitím někdo opraví zdrojovou objednávku, dojde ke konfliktu a uživateli A se nepodaří dodací list uložit.
Ke konfliktům může docházet v obou směrech - neúspěchem může skončit pokus o uložení zdrojového i následného dokladu. Záleží na tom, ke kterým dokladům se v průběhu editace vytvořily zámky, které jsou v okamžiku ukládání stále platné.
Jednu z možných konfliktních situací demonstruje následující příklad.
Příklad...Uživatel A se rozhodne vytvořit Dodací list z objednávky přijaté. V agendě Objednávky přijaté (OP) použije funkci Vytvořit s volbou Dodací list, vyplní požadované údaje v průvodci a v novém okně se mu otevře předvyplněný dodací list.
Zatímco uživatel A předvyplněný dodací list upravuje, uživatel B si otevře stejnou objednávku k editaci (použije funkci Opravit). Uživatel A stále upravuje dodací list.
Uživatel A dokončí úpravy dodacího listu a pokusí se ho uložit. Uložení se mu však nezdaří, protože v rámci transakce je zapotřebí provést změnu zdrojové objednávky přijaté (aktualizovat na ní informaci o čerpání). Uživatel A je o konfliktu informován a požadované změny musí provést znovu (znovu vytvořit dodací list).
Uložení DL se nezdařilo kvůli souběžné editaci zdrojové objednávky.
Pokud by uživatel B editovanou objednávku mezitím uložil (dříve než by se uživatel A pokusil uložit vytvářený následný dodací list), uložení DL by se uživateli A stejně nezdařilo, protože uložení objednávky uživatelem B způsobilo zvýšení její ObjectVersion.
Uložení DL se nezdařilo kvůli neaktuální verzi zdrojového objektu. - Při použití funkce Vytvořit se nevytvoří žádný nový zámek.
- Nekontroluje se případné smazání uzamčeného dokladu provedené jiným uživatelem.
- Kontrola (nikoli vytváření zámků) se vztahuje i na hromadné opravy. Tj. pokud byl rozeditován záznam a někdo se pokusil použít hromadnou opravu nebo hromadnou opravu uživatelských položek téhož záznamu, byl na uzamčení záznamu upozorněn a daný záznam nebyl hromadnou opravou nijak změněn.
- Pesimistické zamykání je (na rozdíl od situace do verze 18.01 / 18.03) ve výchozím nastavení aktivní. V případě potřeby je možné jej deaktivovat (nastavit PessimisticEditLock=0 v sekci [Client] v souboru Nexus.cfg).
-
Okno informující o probíhající editaci záznamu nově obsahuje identifikaci uživatele, který opravu zamčeného záznamu provádí (takže se uživatelé mohou snáze dohodnout mezi sebou).
Okno informující o probíhající editaci záznamu
Zobrazení detailů po stisku tlačítka Více v předchozím okně
- Zámek se sice vytváří na úrovni uživatelského rozhraní, ale jeho existence je kontrolována na úrovni business objektů, tj. pokud zámek vznikne v souvislosti s použitím funkce Opravit v aplikaci ABRA Gen, po dobu trvání zámku není možné záznam přepsat ani jinými způsoby (skriptování, Web API, OLE).
- Doba trvání zámku (timeout) může být (ve výchozím nastavení je) časově omezena:
- výchozí nastavení je 10 minut, tuto hodnotu je možné změnit nastavením parametru BusinessObjectEditLockTimeOut v sekci [Client] v souboru Nexus.cfg. Hodnota se zadává v milisekundách (BusinessObjectEditLockTimeOut=900000 znamená vytvoření zámku s časovou platností 15 minut, BusinessObjectEditLockTimeOut=-1 znamená časově neomezený zámek).
hodnota BusinessObjectEditLockTimeOut nastavená v souboru Nexus.cfg je platná i pro skriptování a Web API (pokud se rozhodnete zámky vytvářet i prostřednictvím těchto rozhraní)
-
Je k dispozici speciální privilegium Opravovat záznamy, které již opravuje jiný uživatel umožňující oprávněným uživatelům převzetí editačního zámku (stejné privilegium má k dispozici také Supervisor)
Příklad hlášení při pokusu o souběžnou editaci téhož záznamu s aktivovanou simulací pesimistického zamykání s možností převzetí zámku (přihlášený uživatel má k dispozici privilegium Opravovat záznamy, které již opravuje jiný uživatel).
-
Mechanismus simulace pesimistického zamykání je zohledňován také při oceňování skladových pohybů (spouštěného z agendy Uzávěrka skladu i v rámci selektivního oceňování).
- Oceňování přeskakuje doklady, které se aktuálně opravují (existuje k nim platný editační zámek).
-
Oceňování vytváří krátkodobé zámky na aktuálně přeceňované doklady, aby se zabránilo pokusům o jejich souběžnou editaci jinými uživateli.
Řádky jsou aktualizovány po dávkách a doba trvání těchto zámků odpovídá době, za kterou se přecení jedna dávka, momentálně 100 řádků.
Princip kontroly, zda došlo ke kolizi
ABRA Gen si u každého záznamu v databázi (např. záznam střediska), pamatuje jednak jeho interní identifikátor ( "ID") a dále číslo verze tohoto záznamu ( tzv. "ObjectVersion", budeme jej označovat "OV"). Pokud dojde ke změně záznamu, systém automaticky v databázi zvyšuje číslo verze. Podle tohoto čísla se pak rozpoznává, zda byl objekt změněn či nikoli.
Ke kolizím tedy může dojít:
- při běhu v síťové instalaci
- při běhu ve dvou oknech
- ale i při běhu v jednom okně při otevření téže agendy vícekrát
Objasníme na příkladu:
Příklad 3: Mějme otevřenu agendu faktur vydaných ve dvou oknech. V obou v záložce Omezení provedeme dotaz a načteme fakturu FV-100. Tato faktura nechť má ID=ABC345 a OV=1 (ve skutečnosti vypadá jinak). V prvním okně provedeme opravu faktury a uložíme. V ten moment se zvýší číslo verze, tedy OV=2. V druhém okně provádíme opravu téže faktury a uložíme. Systém porovnáním verzí zjistí, že se pokoušíme uložit "starší" záznam s OV=1, který byl mezitím změněn. Nahlásí kolizi.
Vznik takové kolize můžeme rovněž objasnit i na následujícím zjednodušeném schématu. Modrá barva znázorňuje stav dat dvou současně pracujících uživatelů, červená stav v databázi. Písmena S/C/R demonstrují S=start, C=commit a R=rollback transakce. V databázi nechť je záznam s ObjectVersion OV=1. Uživatel 1 zahájí operaci oprava záznamu. Tudíž zahájí a ukončí se transakce, v rámci níž se načtou data z databáze (select), záznam má OV=1. Poté uživatel 1 provádí požadované změny. Mezitím Uživatel 2 zahájí opravu téhož záznamu (což může, jelikož daný záznam není prvním uživatelem nijak uzamčen, tj. i jemu se provede v rámci krátké transakce select z databáze, záznam má stále OV=1. Poté Uživatel 1 vyvolá uložení svých oprav do databáze. Zahájí se pro něj transakce, v rámci níž se provede uložení dat (update) a která úspěšně proběhne. Po jejím potvrzení bude mít záznam v databázi OV=2. Pokud poté druhý uživatel vyvolá uložení svých oprav, zahájí se i jemu transakce pro uložení dat do databáze (update), ale tato skončí neúspěšně, jelikož se mezitím změnil ObjectVersion. Uživateli 2 se provede nový select z databáze (občerstvení dat), tj. bude mít OV=2 a teprve poté bude moci znovu zadat své změny a záznam uložit (poté záznam získá OV=3 atd.).
Zjednodušený schématický náčrt průběhu transakcí dvou současně pracujících uživatelů.Transakce 1, 2 a 3 proběhly úspěšně (naznačeno barevnou výplní) a byly potvrzeny (commit). Transakce 4 neproběhla úspěšně a byl proveden rollback.
Kolizím tohoto typu lze většinou předcházet průběžným prováděním Občerstvování! Pro editaci záznamu navíc platí, že před zahájením editace si systém občerství automaticky! Tj. při zahájení editace systém zjistí, zda nedošlo ke změně ObjectVersion záznamu, který chceme editovat (což mohlo nastat např. tak, že jiný uživatel na síti daný záznam změnil a uložil, ale aktuální uživatel si od té doby neprovedl občerstvení). Jestliže záznam byl změněn, tak zobrazí informační hlášení, a provede automaticky občerstvení daného záznamu, poté jej teprve nabídne k editaci v editačním režimu.
Tím se tedy případy vzniku kolizí minimalizují, nicméně i přes to k nim může dojít. Případům, kdy dva uživatelé rozeditují jeden a tentýž záznam, lze bránit simulací pesimistického zamykání, viz výše.
Většina kolizí tohoto typu vyžaduje interakci uživatele. Tj. pokud dojde při ukládání ke kolizi, jelikož záznam byl změněn jiným uživatelem nebo i týmž uživatelem z jiné spuštěné úlohy ABRA Gen případně z agendy spuštěné v samostatném okně apod. a neproběhlo občerstvení, program uživatele na tuto skutečnost upozorní informačním hlášením, ve kterém případně uživateli oznámí, že z databáze byla načtena nová data (aktuální stav z databáze) a uživatel musí své změny zadat a uložit znovu.
Příklad hlášení při pokusu o uložení dokladu, který byl mezitím změněn jiným uživatelem.
Speciálně ošetřené situace - Nicméně jsou v systému případy, kdy takové situace jsou speciálně ošetřeny, např. při pokusu dvou uživatelů současně provést pohyb na téže dílčí skladové kartě (např. oba vystavují a ukládají dodací list s danou kartou). Na dílčí kartě se zaznamenává aktuální počet, tudíž po uložení prvního DL se mj. změní ObjectVersion dílčí karty, tedy druhému uživateli by pokus o uložení dle předchozího schématu výše selhal. V tomto případě se systém sám vzápětí znovu pokusí o uložení (tj. zjednodušeně řečeno: "zapamatuje si" požadovanou změnu (změnu počtu), provede select aktuálního stavu z databáze (tj. s OV=2), "zapíše" sám "zapamatovanou" změnu (tj. jeho OV bude 3) a provede nový pokus o uložení (update). Pokud bude úspěšný, transakce s update se ukončí a záznam dílčí karty bude mít i v databázi OV=3 (tedy následně i druhý uživatel bude moci uložit svůj dodací list). Toto probíhá na pozadí bez interakce uživatele (i když není zapnuta simulace pesimistického zamykání), nicméně pouze po nějakou definovanou dobu (cca 1 minuta). Pokud se během tohoto intervalu nezdaří uložení (jiní uživatelé s danou dílčí kartou neustále pracují), systém pokusy ukončí a oznámí uživateli neúspěšný pokus o uložení dodacího listu.
Zjednodušený schématický náčrt průběhu transakcí a pokusu systému změny uživatele 2 uložit, přestože se díky zásahu jiného uživatele mezitím změnil OV záznamu v databázi (navazuje na předchozí schéma, viz výše). Transakce 4 neproběhla úspěšně (viz předchozí schéma). Následuje časový interval, v němž se systém opakovaně pokusí o uložení, tj. načte aktuální stav, zaznamená změnu a pokusí se uložit (transakce 5 a 6).
Kolize při pokusu o uložení mohou nastat nejen v případě, že byl současně změněn týž záznam (současná změna ObjectVersion), jak bylo řečeno výše, ale i v případě, že se pokoušíme uložit záznam s odkazem na jiný záznam aktuálně "držený" nějakou jinou transakcí.
Jak plyne ze schématických obrázků výše, většina transakcí prováděných v ABRA Gen je velmi krátkých, a tudíž je u nich pravděpodobnost vzniku takovýchto kolizí velmi malá. Nicméně existují v systému i případy, kdy akce probíhá v rámci jedné dlouho trvající transakce (uzávěrka skladu, zálohování dat apod.).
Pokud se v rámci nějaké dlouhotrvající transakce pracuje s nějakým objektem, a uživatel se mezitím pokusí o uložení záznamu, který se na tentýž objekt odkazuje, pokus o uložení selže.
Příkladem může být právě uzávěrka skladu, která mění objekt sklad. Jelikož však trvá dlouho, je objekt sklad delší dobu nedostupný pro použití z následných transakcí. (Databáze nemůže povolit uložit záznam odkazující se na takový objekt, jelikož předchozí transakce jej může obecně třeba i smazat či změnit jeho interní identifikátor, přes který se realizuje odkaz apod., tudíž při pokusu o uložení databáze čeká a, pokud se v rámci určitého časového intervalu nedočká, pak oznámí chybu.):
Zjednodušený schématický náčrt pokusu o uložení změn s odkazem na objekt sklad "držený" transakcí skladová uzávěrka. Transakce 3 neproběhne úspěšně, ačkoliv se samotný ObjectVersion objektu sklad během změn provedených uživatelem 2 nezměnil a je stále 2.
Popsaný problém se týká pouze uzávěrek (k datu a za období). Při oceňování skladových pohybů se aktuálně editované doklady přeskakují.
Transakce se "zjednodušeně řečeno" ukládají po ukončení akce v pořadí, v jakém byly zahájeny (myslí se transakce zahájené v daném "connectionu" na databázi). Přičemž pokud jedna transakce není ukončena, nelze uložit ani následující. Proto, pokud probíhá nějaká transakce, nelze se přepnout v rámci dané spuštěné úlohy ABRA Gen do jiné agendy (a to ani otevřené v jiném samostatném okně, i když by šlo o jiný samostatný "connection" na databázi), aby v rámci dané spuštěné úlohy ABRA Gen nedocházelo zbytečně ke kolizím (pokud by z nějakého důvodu po ukončení např. akce uzávěrky nebyla ukončena transakce uzávěrky, nebylo by možno uložit ani případné transakce, které byste provedli poté).
V průběhu dlouho trvajících transakcí může docházet v systému ke změnám jedině tehdy, pokud systém provozujete síťově a změnu provádíte z jiné spuštěné úlohy ABRA Gen.
Pokud spustíte nějakou časově náročnou akci a současně chcete pracovat v jiné agendě, pak si musíte spustit požadovanou agendu v rámci druhé spuštěné úlohy ABRA Gen (síťově).
Dále zde zmíníme případ zálohování, které je jednou z dlouho trvajících transakcí. Při zálohování platí, že do zálohy se zahrnou JEN ta data, která byla pořízena v okamžiku zahájení transakce zálohování. Tedy, pokud byste v průběhu zálohování např. opravili nějaký doklad, vystavili nový apod., daná změna nebo nový doklad v záloze obsažen nebude! A to ani v případě, že byste tuto změnu provedli ještě dříve, než přijde "na řadu" zálohování tabulky databáze, v níž byla daná změna provedena!