DB Lock: Eskalace zámků

V minulém článku jsme si představili typy zámků a jejich vzájemnou kompatibilitu. Víme také, co znamená granularita. Díky aplikaci zámků nad řádky můžeme číst v jedné transakci data, zatímco druhá transakce v tabulce mění jiné řádky. Nicméně vše má své meze. Také jsme si již řekli, že každý zámek má nějakou režii. V případě, že počet aplikovaných zámků nad řádky tabulky je tak velký, že se vyplatí spíše zamknout celou tabulku, provede se tzv. eskalace zámků (Lock Escalation). Celý princip tedy spočívá v nahrazení mnoha dílčích zámků jedním zámkem na vyšší úrovni.

Podívejme se na první příklad:

SELECT TOP (700) * FROM Production.Location3

Když spustíme jednoduchý dotaz, který zobrazí prvních 700 řádků, provede se zamčení stejného množství řádků. Jednotlivé řádky mají (S) zámek, tabulka jako nadřazený objekt má (IS). Aplikované zámky můžeme vidět na následujícím obrázku.

Row Lock

Nyní změníme počet zobrazovaných řádků.

SELECT TOP (7000) * FROM Production.Location3

Po spuštění zjistíme, že se aplikoval jeden zámek (S) na celou tabulku.

Table Lock

V rámci zachycených událostí si můžeme povšimnout, že se objevila událost lock_escalation

Escalation Event

Ve skutečnosti se začaly uzamykat řádky. V momentě kdy s ohledem na vlastní režii zámků začalo být vhodné zamknutí tabulky, SQL server se pokusil zámek „povýšit“.

Zkusme zjistit, co se stane pokud povýšení zámků z řádků na celou tabulku z nějakého důvodu selže. Navodit tuto situaci si lze velice jednoduše. Vzpomeňme si na kompatibilitu zámků, kdy zámek Intent Exclusive (IX) je nekompatibilní se Share (S) zámkem. Z předešlého příkladu vidíme, že pro eskalaci zámku budeme potřebovat pro zamčení celé tabulky Shared (S) zámek. Dále víme, že pokud se někde pokusíme provést UPDATE řádku, aplikuje se nejprve na tabulku Intent Exclusive (IX) zámek a teprve po té se aplikuje Exclusive (X) zámek na námi měněný řádek. Spustíme si tedy první transakci, která bude měnit řádek

BEGIN TRANSACTION T1

UPDATE Production.Location3 WITH (ROWLOCK) SET ModifiedDate = GETDATE() WHERE LocationID = 10000
WAITFOR DELAY '00:00:10:00'

ROLLBACK TRANSACTION T1

Zatímco běží první transakce, pustíme si dotaz, který přečte prvních 7000 řádků. Tento dotaz nám v předchozím příkladě spolehlivě provedl eskalaci a zamčení celé tabulky.

SELECT TOP (7000) * FROM Production.Location3

Nyní se podíváme, jaké se aplikovaly zámky

Exclusive Lock

Na obrázku vidíme, že první transakce běžící pod session_id = 77 zamkla měněný řádek tabulky a na tabulku aplikovala IX zámek. Druhá transakce běžící pod session_id = 78 nemohla provést eskalaci zámku z řádku na tabulku, protože požadovaný S zámek není kompatibilní s IX zámkem. Dotaz proto aplikoval S zámek na všech 7000 řádků. Závěrem tedy můžeme konstatovat, že pokud se z jakéhokoli důvodu nepodaří zamknout celou tabulku, pokračuje se i nadále v zamykání řádků a eskalace se neprovede.

Eskalace zámků se vždy provádí tak, že zámek řádků se povýší na zámek celé tabulky. Stejně tak zámek stránek se povýší na zámek celé tabulky. U tabulek, které jsou rozděleny na části (partitions), se ve výchozím nastavení eskaluje zámek na celou tabulku. Nicméně změnou nastavení způsobů eskalace pomocí

ALTER TABLE <TableName> SET (LOCK_ESCALATION = AUTO)

lze nastavit zamknutí příslušné části (partition) místo zamknutí celé tabulky.