AX: Podpora pesimistických zámků

Vynechme úvod a skočme rovnou do kódu:

while select forUpdate myTable
{
    ttsBegin;
    myTable.MyField = 'something';
    myTable.update();
    ttsCommit;
}

S takovým kódem se setkávám opravdu často a za určitých okolností funguje velmi dobře. Konkrétně vyžaduje, aby bylo aktivní optimistické zamykání záznamů. Dynamics AX v takovém případě jen zkontroluje, že je záznam vybrán pro update a že je metoda update() volána v transakci, což je splněno a vše funguje.

Pokud ale tabulka používá pesimistický zámek, update selže s následující run-time chybou:

Cannot edit a record in MyTable.
Cannot call NEXT, update(), or delete() on buffer where data is selected or inserted in another transaction scope.
Calls to NEXT, update(), or delete() must be performed on the buffer on the selection transaction level, or within the same transaction (TTS) scope.

Můj pokus o programátorčeštinu (nemám nikde českou AX, takže nemůžu poskytnout oficiální překlad):

Nelze editovat záznam v MyTable.
Nelze volat NEXT, update(), nebo delete() na bufferu v kterém byla data vybrána nebo vložena v jiném rozsahu transakce.
Volání NEXT, update(), or delete() musí být provedeno na stejné úrovni transakce jako select, nebo ve stejném rozsahu transakce (TTS).

To se stane proto, že pesimistické zamykání potřebuje umístit zámek na vybíraný záznam a to se musí stát ve stejné transakci, což zde evidentně není splněno.

Optimistické zamykání je dostupné od Dynamics AX verze 4 (tudíž výše uvedený příklad nebude fungovat např. v Axaptě 3) a je to nyní výchozí způsob zamykání záznamů. Nicméně říkám výchozí, ne jediný možný – pesimistické zamykání lze aktivovat následujícími způsoby:

  1. Pro jednotlivý dotaz (select pessimisticLock myTable nebo myTable.concurrencyModel())
  2. Pro jednotlivou tabulku (vlastnost OccEnable = No)
  3. Globálně pro celou AX (Administration > Setup > System > Concurrency model configuration (AX4, AX2009), respektive System administration > Setup > Database > Select concurrency mode (AX2012))

V takovou chvíli výše uvedený kód selže. Jinak řečeno, ten kód funguje pouze v konkrétní konfiguraci Dynamics AX, jinak končí run-time výjimkou.

Otázka zní – je nutné psát databázové dotazy tak, aby fungovaly i s jinou konfigurací zámykání?

Tvrzení č. 1: Konfigurace zamykání je standardní součást Dynamics AX a neměli bychom svévolně omezovat existující funkcionalitu. Pesimistické zámky mohou pomoci vyřešit nadměrné množství konfliktů při zápisech v některých částech aplikace apod.

Tvrzení č. 2: Změna způsobu zamykání v existující aplikaci je zcela výjimečná a nestojí za to ji podporovat, protože to může mít zbytečné dopady na výkon.

Poznámka k tvrzení číslo 2 – v našem příkladu bychom buď museli vybrat každý záznam pro update zvlášť:

while select forUpdate myTable
{
    ttsBegin;
    myTable.reread(); //přenačtení záznamu ve stejném bufferu
    myTable.MyField = 'something';
    myTable.update();
    ttsCommit;
}

nebo změnit transakční logiku tak, že celý cyklus bude jedna atomická operace:

ttsBegin;
while select forUpdate myTable
{
    myTable.MyField = 'something';
    myTable.update();
}
ttsCommit;

První přístup významně zvyšuje počet dotazů do databáze, druhý vyžaduje změnu transakční logiky a potenciálně zamyká velké množství záznamů.

Můj názor by se dal shrnout asi takto:

  1. Je rozumné předpokládat, že nikdo nebude měnit způsob zamykání napříč celým systémem a není proto nutné podporovat  pesimistické zamykání pro všechny tabulky (pokud znáte protipříklad, rád si rozšířím obzory).
  2. Pro každou tabulku rozhodněte, zda by měla pesimistické zamykání podporovat a zdokumentujte, pokud ano. Takových tabulek nebude mnoho a řada implementací skutečně nepotřebuje žádné. Na druhou stranu, někdy je to kritická vlastnost.
  3. Pokud má tabulka podporovat pesimistické zamykání, vytvořte a otestujte ji nejprve s OCCEnabled na No.
  4. U každého řešení předpokládejte, že pesimistické zamykání nepodporuje, dokud se neprokáže opak.

Je chyba pesimistické zamykání zcela ignorovat, jak se často děje.