DB Lock: Granularita a Hierarchie

V běžném provozu se stává, že k jedné informaci uložené v databázi přistupuje několik klientů současně. V případě že všichni klienti chtějí informaci pouze číst, nenastává žádný problém. Potíže ovšem nastávají v případě, kdy jeden klient chce data číst a druhý je zároveň měnit. SQL Server proto implementuje zamykání řádků (Row Lock), stránek (Page Lock) či rovnou celých tabulek (Table Lock).
Databázových zámků existuje několik typů a o celé jejich řízení se stará Lock Manager. V jednoduchosti si celý princip zámku představme tak, že na začátku transakce jsou data zamčena a při ukončení transakce provedením COMMIT nebo ROLLBACK jsou všechny zámky odstraněny.

Lock Granularity

SQL server se snaží uzamykat data tak, aby zbytečně nesnižoval souběžný přístup více klientů k datům a zároveň aby počet zámků nezatěžoval server jako takový. Každý zámek a jeho správa odebírá SQL serveru část dostupných zdrojů jako je CPU, paměť atp. Čím více zámků je v systému aktivních, tím více je potřeba zdrojů na jejich režii. Jak jsem již předeslal, data mohou být uzamčena na úrovni řádku, stránky nebo tabulky. Přičemž platí, že největší možnost souběžného přístupu k datům je při úrovni zámku na řádku a nejmenší v případě zamčení celé tabulky.

Nejprve si představme strukturu tabulky, kterou v další části článku použijeme k demonstraci zámků. V databázi existuje tabulka Location3. Nad tabulkou existuje primární klíč PK_Location3_LocationID. Tabulka obsahuje dva indexy. První clusterovaný index náleží primárnímu klíči a je nazván PK_Location3_LocationID, druhý index IX_Name_ModifiedDate zahrnuje dva sloupce, jak je patrné z názvu. Postupně budeme souběžně spouštět dvě transakce a zkoumat, co se nad tabulkou děje. Obě transakce jsou spouštěny ze samostatných oken v SQL Management Studiu. Na konci každé transakce provádím rollback, abych si nezměnil data. První transakce má před rollbackem čekání 20 vteřin.

Následující tabulky ilustrují jak vypadají řádky uložené v databázi. Z tabulky Location3 nás budou zajímat pouze tyto tři řádky, jež budeme měnit.

Tabulka Location3
LocationID Name CostRate Availability ModifiedDate
1 A1 2 100 21.09.16 18:24
2 A2 2 100 21.09.16 18:25
22000 B2000 2 100 21.09.16 18:35

Dále se nám bude hodit znalost čísel stránek, jež obsahují uložené řádky pro oba existující indexy. Pro PK platí, že řádek A1 a A2 nalezneme uložené ve stránce číslo 36760 a budeme-li chtít vyhledat řádek B2000 podle druhého indexu IX, musíme mít přístup ke stránce číslo 54763.

Index PK_Location3_LocationID
Name PageID
A1 36760
A2 36760
B2000 37290
Index IX_Name_ModifiedDate
Name PageID
A1 54665
A2 54670
B2000 54763

Row Lock

První transakce bude provádět změnu data v řádku kde Name = ‚A1‘

BEGIN TRANSACTION T1
UPDATE Production.Location3 WITH (ROWLOCK) SET ModifiedDate = GETDATE() WHERE Name = 'A1'
WAITFOR DELAY '00:00:20:00';
ROLLBACK TRANSACTION T1

Druhá transakce se zároveň pokusí provést změnu data v řádku kde Name = ‚A2‘

BEGIN TRANSACTION T2 
UPDATE Production.Location3 WITH (ROWLOCK) SET ModifiedDate = GETDATE() WHERE Name = 'A2' 
ROLLBACK TRANSACTION T2

Výsledkem je téměř okamžité provedení druhé transakce a po 20 vteřinách dokončení první transakce. Podívejme se blíže na zámky, které se v testu objevily:

Row Lock T1

Transakce T1 v souladu s nastavením (ROWLOCK) aktivovala zámky nad řádkem resp. indexem, které se promítly do exkluzivního zámku (X). Na obrázku můžete vidět zámky na řádku 2 – 4. Povšimněte si prosím řádku 6 a 7. Zde jsou uvedena čísla stránek, kde jsou uložená vlastní data tabulky.

Obdobně vypadá i výstup pro transakci T2:

Row Lock T2

Jelikož se obě transakce ve změně obsahu tabulky nikde nepřekrývaly (různé hashe u zámku X) a díky vysoké granularitě zámku, mohly obě změny proběhnout zároveň.

Page Lock

Ve druhém příkladu změníme způsob zamykání na Page Lock. Stačí nahradit ROWLOCK za PAGLOCK. To znamená, že exkluzivní zámek nyní bude nad stránkou (PAGE). Z výše uvedených obrázků je patrné, že obě transakce ke svému provedení potřebují změnit stránku číslo 36760, kde jsou uloženy oba řádky tabulky, které chceme měnit. Mělo by tedy dojít k pozastavení druhé transakce T2 do té doby, dokud neskončí první transakce T1.

Podívejme se na zámky, které se nám objevily nad tabulkou:

Page Lock T1

Na obrázku vidíme, že exkluzivní zámky jsou nad stránkami. Tedy druhá transakce musí čekat na uvolnění požadované stránky první transakcí.

Page Lock T2

Změnou způsobu zamykání dat jsme zmenšili granularitu na úroveň stránek a tedy místo zamčení jednoho řádku tabulky jsme docílili zamčení všech řádků tabulky, které jsou uloženy ve stejné stránce. Pokračujme však dále v našem zkoušení. Pokud změníme druhou transakci tím, že budeme měnit datum řádku, který je uložen v jiné stránce, měla by transakce proběhnout téměř okamžitě jako při zámku nad řádkem.

První transakci tedy necháme nezměněnou a druhou změníme následovně:

BEGIN TRANSACTION T2
UPDATE Production.Location3 WITH (ROWLOCK) SET ModifiedDate = GETDATE() WHERE Name = 'B2000'
ROLLBACK TRANSACTION T2

Dle našeho předpokladu, druhá transakce nyní proběhla bez čekání na ukončení první transakce. Na obrázku můžeme zkontrolovat požadované zámky. Srovnáme-li čísla zamčených stránek obou transakcí, zjistíme, že jsou rozdílná.

Page Lock T2 Different Pages

Table Lock

V našem posledním příkladě snížíme ještě více granularitu zámků a v obou transakcích zamkneme pro jistotu celou tabulku. Vezmeme skripty z prvního příkladu a zaměníme ROWLOCK za TABLOCK.

Díky zámku nad celou tabulkou bude druhá transakce vždy čekat dokud neskončí první transakce. Požadavek obou transakcí na zámky jsou shodné a můžete je vidět na následujícím obrázku:

Table Lock

Lock Hierarchy

V předchozích příkladech jste si mohli povšimnout předávání informace o zámku z jedné úrovně na druhou. Vyšší úroveň je tedy informována o tom, že někde níže probíhá transakce.
Při zámku nad řádkem (Row Lock), je zamčen řádek exkluzivním zámkem (X), který zakazuje přístup k danému řádku. Stránka která obsahuje zamčený řádek dostane příznak IX. Tento typ zámku umožňuje i nadále přístup ke stránce. Stejně tak i celá tabulka bude mít na sobě zámek IX.

Table, Page, Row Lock Hierarchy

V příštím díle se budeme podrobněji věnovat jednotlivým typům zámků.