Archiv pro štítek: zámky

Zakládání ID u tabulek bez IDENTITY

Máme historickou tabulku, která má umělé id, ale nemá na něm IDENTITY. Nechceme procházet očistcem a převádět ji na tabulku s IDENTITY, ale chceme zakládat ID tak, aby jsme se vyhnuli chybě „Violation of PRIMARY KEY constraint“ při souběhu ze dvou stran.

Ta historická tabulka může vypadat třeba takto:

CREATE TABLE dbo.tabulka(
	id_tabulka INT CONSTRAINT pk_tabulka PRIMARY KEY CLUSTERED,
	data VARCHAR(4000)
)

A špatný přistup, jak zakládat záznamy třeba takto:

BEGIN TRAN

DECLARE @id INT = (SELECT ISNULL(MAX(t.id_tabulka),0)+1 FROM dbo.tabulka t)

--jen pro zvýraznění problému
WAITFOR DELAY '0:0:10'

INSERT INTO dbo.tabulka
(
    id_tabulka,
    data
)
VALUES
(   @id,
    'data'
)
COMMIT

Toto když spustíme ve dvou oknech, tak nám to ve druhém upadne na „Violation of PRIMARY KEY constraint“, protože se pokusíme založit podruhé stejný primární klíč.

Možnosti řešení jsou dvě. Pokud to máme v transakci, tak můžeme použít UPDLOCK. Ten nám uvalí U zámek na poslední záznam v tabulce a druhá transakce ho tak nebude moct přečíst a zůstane čekat do doby, než první transakce doběhne do konce.

BEGIN TRAN

DECLARE @id INT = (SELECT ISNULL(MAX(t.id_tabulka),0)+1 FROM dbo.tabulka t WITH (UPDLOCK))

--jen pro zvýraznění problému
WAITFOR DELAY '0:0:10'

INSERT INTO dbo.tabulka
(
    id_tabulka,
    data
)
VALUES
(   @id,
    'data'
)
COMMIT

Druhou možností je, že v případě chyby zkusíme to maximální id vypočíst znova. Z opatrnosti tomu dáme jen 5 pokusů a pak toho případně necháme a chybu vypropagujem.

DECLARE @counter INT = 0;
DECLARE @max_counter INT = 5;
DECLARE @id INT = (SELECT ISNULL(MAX(t.id_tabulka),0)+1 FROM dbo.tabulka t WITH (UPDLOCK))
WHILE @counter < @max_counter
BEGIN
  BEGIN TRY
    SET @id = (SELECT ISNULL(MAX(t.id_tabulka),0)+1 FROM dbo.tabulka t)

    --jen pro zvýraznění problému
    WAITFOR DELAY '0:0:10'

    INSERT INTO dbo.tabulka
    (
      id_tabulka,
      data
    )
    VALUES
    (	
      @id,
      'data'
    )

    SET @counter = @max_counter; --vypadnout z cyklu
  END TRY
  BEGIN CATCH
    SET @counter += 1;
    IF ERROR_NUMBER() <> 2627 --Violation of PRIMARY KEY constraint
      OR @counter = @max_counter
        THROW;
  END CATCH;
END;

První řešení mi přijde čistší, ale zase tam druhá transakce musí čekat na dokončení první.

Jak na UPSERT

Často potřebujeme podle určitého klíče zaktualizovat hodnotu. A pokud klíč neexistuje, tak ho s hodnotou vložit do tabulky. Tedy UPDATE nebo INSERT…UPSERT. A často nás na první dobrou napadne úplně špatné řešení.

CREATE TABLE dbo.tabulka(
	id INT IDENTITY(1,1) NOT NULL CONSTRAINT pk_tabulka PRIMARY KEY CLUSTERED, 
	dalsiKlic VARCHAR(50) NOT NULL CONSTRAINT uq_tabulka_dalsiKlic UNIQUE, 
	data VARCHAR(50) NULL
)
GO

CREATE OR ALTER PROCEDURE dbo.p_velky_spatny_UPSERT
	@dalsiKlic VARCHAR(50)
	,@data VARCHAR(50)
AS
BEGIN
	IF EXISTS(
		SELECT * 
		FROM  dbo.tabulka t
		WHERE t.dalsiKlic = @dalsiKlic
	)
		UPDATE dbo.tabulka
		SET data = @data
		WHERE dalsiKlic = @dalsiKlic
	ELSE
		INSERT INTO dbo.tabulka
		(
			dalsiKlic,
			data
		)
		VALUES
		(   @dalsiKlic,
			@data
			)
END
GO

Tedy najdi záznam a pokud jsi našel, tak ho najdi znova a zaktualizuj. No a pokud jsi ho nenašel, tak ho založ. Problém je, že pokud záznam existuje, tak ho hledám dvakrát a plýtvám tak zdroji. Druhý problém je, že ta operace není atomická a v READ COMMITED isolation levelu se může stát, že dvě transakce najednou ověří, že záznam s daným klíčem neexistuje a obě se ho pak pokusí založit. V lepším případě pak skončíme s chybou Violation of UNIQUE KEY constraint…, v horším založíme duplicitu (pokud nám chybí constraint). Zabalit to do transakce nic neřeší.

Zabalit to do transakce a zvýšit isolation level na serializable řešením je:

CREATE OR ALTER PROCEDURE dbo.p_velky_spatny_serializable_UPSERT
	@dalsiKlic VARCHAR(50)
	,@data VARCHAR(50)
AS
BEGIN
	SET TRAN ISOLATION LEVEL SERIALIZABLE
	BEGIN TRAN
	IF EXISTS(
		SELECT * 
		FROM  dbo.tabulka t
		WHERE t.dalsiKlic = @dalsiKlic
	)
		UPDATE dbo.tabulka
		SET data = @data
		WHERE dalsiKlic = @dalsiKlic
	ELSE
		INSERT INTO dbo.tabulka
		(
			dalsiKlic,
			data
		)
		VALUES
		(   	@dalsiKlic,
			@data
		)
	COMMIT
END
GO

Ale tím vyměníme problém se zakládáním duplicit, za problém s deadlocky. U neexistující položky provede select v existu zamčení RangeS-S zámkem, čímž si pojistí, aby daná hodnota nebyla vložena v jiné transakci. Problém je, že RangeS-S je kompatiblní s RangeS-S. Tedy, pokud se dvě transakce sejdou a obě provedou EXISTS podmínku, tak si obě můžou hodit na stejný rozsah záznamů RangeS-S zámek. Následně při insertu se pokusí použít X zámek, čímž dojde k deadlocku.

Jak je to správně?
Správně to je v transakci, v serializable a bez zbytečného selectu.

CREATE OR ALTER PROCEDURE dbo.p_dobry_UPSERT
	@dalsiKlic VARCHAR(50)
	,@data VARCHAR(50)
AS
BEGIN
	SET NOCOUNT ON;

	BEGIN TRAN
	UPDATE dbo.tabulka WITH (SERIALIZABLE)
	SET data = @data
	WHERE dalsiKlic = @dalsiKlic

	IF @@ROWCOUNT = 0
		INSERT INTO dbo.tabulka
		(
			dalsiKlic,
			data
		)
		VALUES
		(   	@dalsiKlic,
		    	@data
		)
	COMMIT
END
GO

Pokud záznam existuje, tak se provede update. Pokud neexistuje, tak po updatu zůstane na unikátním indexu RangeS-U zámek, který zabezpečí, že si jiný proces neuvalí stejný zámek a zároveň, že jiný proces nevloží hodnotu klíče, o které jsme se právě přesvědčili, že neexistuje. Tedy žádné deadlocky a žádné Violation of UNIQUE KEY constraint.

UPDATE WITH (UPDLOCK)

Ve zděděném kódu občas potkávám UPDATE tabulka WITH (UPDLOCK) SET… a nikdo mi k tomu nechtěl říct jestli to je k něčemu dobré. Teorie praví, že pro vyhledání updatovaného záznamu se použije U zámek a updatovaný záznam pak dostane X zámek, který se drží do konce transakce. Hint WITH (UPDLOCK) by měl zaručit držení U zámku do konce transakce. Pojďme si to zkusit v praxi.

Zkoušet to budeme na 3 tabulkách. Za prvé na haldě bez primárního klíče. Za druhé na haldě s neklastrovaným primárním klíčem. A za třetí na tabulce s klastrovaným primáním klíčem, kterou ještě okořeníme druhým indexovaným klíčem.

CREATE TABLE dbo.halda(
	id INT NOT NULL, 
	data VARCHAR(500) NULL
)

CREATE TABLE dbo.haldaPK(
	id INT NOT NULL CONSTRAINT pk_haldaPK PRIMARY KEY NONCLUSTERED, 
	data VARCHAR(500) NULL
)

CREATE TABLE dbo.tabulka(
	id INT NOT NULL CONSTRAINT pk_tabulka PRIMARY KEY CLUSTERED, 
	dalsiKlic VARCHAR(50) NOT NULL CONSTRAINT uq_tabulka_dalsiKlic UNIQUE, 
	data VARCHAR(50) NULL
)
GO

Naplníme nějakými testovacími daty

INSERT INTO dbo.halda(id, data)
VALUES(1,'a'),(2,'b'),(3,'c')

INSERT INTO dbo.haldaPK(id, data)
VALUES(1,'a'),(2,'b'),(3,'c')

INSERT INTO dbo.tabulka(id,dalsiKlic,data)
VALUES(1,'a','dataA'),(2,'b','dataB'),(3,'c','dataC')
GO

A jdeme zkoušet updaty. Nejdříve na haldě.

BEGIN TRAN
UPDATE dbo.halda WITH (UPDLOCK) SET data = 'x' WHERE id = 3
EXEC sp_lock @spid1 = @@SPID
ROLLBACK
/*
spid   dbid   ObjId       IndId  Type Resource                         Mode     Status
------ ------ ----------- ------ ---- -------------------------------- -------- ------
61     32     0           0      DB                                    S        GRANT
61     32     1845581613  0      RID  1:40441:2                        X        GRANT
61     32     1845581613  0      PAG  1:40441                          IX       GRANT
61     32     1845581613  0      TAB                                   IX       GRANT
61     1      1467152272  0      TAB                                   IS       GRANT
61     32767  -571204656  0      TAB                                   Sch-S    GRANT
*/

Hmm, tak na první pokus žádný U zámek.

Zkusíme haldu s primárním klíčem.

BEGIN TRAN
UPDATE dbo.haldaPK WITH (UPDLOCK) SET data = 'x' WHERE id = 3
EXEC sp_lock @spid1 = @@SPID
ROLLBACK
/*
spid   dbid   ObjId       IndId  Type Resource                         Mode     Status
------ ------ ----------- ------ ---- -------------------------------- -------- ------
61     32     0           0      DB                                    S        GRANT
61     32     1861581670  2      KEY  (98ec012aa510)                   U        GRANT
61     32     1861581670  0      RID  1:8089:2                         X        GRANT
61     32     1861581670  0      TAB                                   IX       GRANT
61     32     1861581670  0      PAG  1:8089                           IX       GRANT
61     32     1861581670  2      PAG  1:16177                          IU       GRANT
61     1      1467152272  0      TAB                                   IS       GRANT
61     32767  -571204656  0      TAB                                   Sch-S    GRANT
*/

Tady už se něco událo. Vidíme X zámek na záznamu na haldě a U zámek na záznamu indexu primárního klíče.

Teď zkusme update tabulky s klastrovaným primárním klíčem, kde budeme vyhledávat právě podle toho primárního klíče.

BEGIN TRAN
UPDATE dbo.tabulka WITH (UPDLOCK) SET data = 'x' WHERE id = 3
EXEC sp_lock @spid1 = @@SPID
ROLLBACK
/*
spid   dbid   ObjId       IndId  Type Resource                         Mode     Status
------ ------ ----------- ------ ---- -------------------------------- -------- ------
61     32     0           0      DB                                    S        GRANT
61     32     1909581841  1      KEY  (98ec012aa510)                   X        GRANT
61     32     1909581841  0      TAB                                   IX       GRANT
61     32     1909581841  1      PAG  1:24265                          IX       GRANT
61     1      1467152272  0      TAB                                   IS       GRANT
61     32767  -571204656  0      TAB                                   Sch-S    GRANT
*/

A tentokrát zase žádný U zámek.

Naposledy zkusíme hledat v klastrované tabulce podle jiného klíče než primárního.

BEGIN TRAN
UPDATE dbo.tabulka WITH (UPDLOCK) SET data = 'x' WHERE dalsiKlic = 'c'
EXEC sp_lock @spid1 = @@SPID
ROLLBACK
/*
spid   dbid   ObjId       IndId  Type Resource                         Mode     Status
------ ------ ----------- ------ ---- -------------------------------- -------- ------
61     32     0           0      DB                                    S        GRANT
61     32     1909581841  1      KEY  (98ec012aa510)                   X        GRANT
61     32     1909581841  0      TAB                                   IX       GRANT
61     32     1909581841  2      KEY  (f037bcc414ff)                   U        GRANT
61     32     1909581841  1      PAG  1:24265                          IX       GRANT
61     1      1467152272  0      TAB                                   IS       GRANT
61     32767  -571204656  0      TAB                                   Sch-S    GRANT
61     32     1909581841  2      PAG  1:32353                          IU       GRANT
*/

Zafungovalo to. Updatovaný záznam v klastrovaném indexu je zamčený X zámkem a záznam v unikátním indexu je zamčený U zámkem.

Závěr
Závěrem zatím budiž to, že při použití WITH (UPDLOCK) se U zámek drží do konce transakce na záznamu indexu, podle kterého se vyhledávalo, pokud je tento rozdílný od podkladové struktury, která drží data (halda, klastrovaný index).

Využití
Hodně jsem se snažil najít nějaký pěkný příklad, k čemu je to dobré. Ale našel jsem jen jeden dost pochybný. Pokud mám nějaký kód, který v jedné transakci dvakrát updatuje stejný řádek a navíc je tento kód vykonáván paralelně ve dvou vláknech nad stejným záznamem. V takovém případě může dojít k deadlocku, kterému se dá zabránit právě tím UPDATE WITH (UPDLOCK).

Příkladem budiž:

BEGIN TRAN
UPDATE dbo.tabulka SET data = 'x' WHERE dalsiKlic = 'c'

--nejaka dalsi prace
WAITFOR DELAY '0:0:10'


UPDATE dbo.tabulka SET data = 'x' WHERE dalsiKlic = 'c'
COMMIT

Tohle když spustím ve dvou oknech SSMS, tak mi to v jednom okně upadne na deadlock.

Mechanismus je takový, že se provede první update v prvním okně a na záznamu v klastrovaném indexu je pak držen X zámek. První update ve druhém okně pak provede jen vyhledání záznamu k updatu v unikátním indexu, který si označí U zámkem. Následně se pokusí označit U zámkem i záznam v klastrovaném indexu, což nebude kompatibilní s již uvaleným X zámkem, takže zůstane čekat na jeho uvolnění. Transakce v prvním okně mezitím dojde k druhému updatu. Pokusí se vyhledat záznam pro update v unikátním klíči a uvalit na něj U zámek, což nebude kompatibilní s již uvaleným U zámkem z druhé transakce. Dostaneme se tedy do situace, kdy první transakce čeká na druhou a druhá na první. Deadlock. SQL server jednu vybere a sestřelí.

Předejít tomu jde přidáním WITH (UPDLOCK) na první update.

BEGIN TRAN
UPDATE dbo.tabulka WITH (UPDLOCK) SET data = 'x' WHERE dalsiKlic = 'c'

--nejaka dalsi prace
WAITFOR DELAY '0:0:10'


UPDATE dbo.tabulka SET data = 'x' WHERE dalsiKlic = 'c'
COMMIT

V takovém případě zůstane po prvním updatu v prvním okně držen X zámek na záznamu v klastrovaném indexu a U zámek na záznamu v unikátním indexu. Transakci v druhém okně se už nepovede přidat U zámek na záznam v unikátním indexu a zůstane tedy čekat. Zároveň už nebude nijak omezovat transakci z prvního okna v úspěšném dokončení.

Jak ovlivnit zámky na úrovni definice tabulky nebo indexu

LOCK_ESCALATION

SQL server využívá eskalaci zámků na vyšší úroveň aby ulevil režii se zámky. Například, pokud server drží příliš mnoho zámků na řádcích, pak dojde k eskalaci zámků na stránku a zámky na řádcích jsou uvolněny.
Eskalace zámků může způsobit blokování zdrojů, protože takto může být zamčeno více řádků než je nutné.

Na úrovni tabulky můžeme ovlivnit, jestli server bude eskalaci zámků používat.

--vypne eskalaci zámků pro většinu případů
ALTER TABLE tabule SET (LOCK_ESCALATION = DISABLE)

--eskalace na úroveň partition (pokud existuje) nebo tabulky
ALTER TABLE tabule SET (LOCK_ESCALATION = AUTO)

--eskalace na úroveň tabulky
ALTER TABLE tabule SET (LOCK_ESCALATION = TABLE)

ALLOW_PAGE_LOCKS, ALLOW_ROW_LOCKS

Na úrovni indexu (a to i clusterovaného) můžeme ovlivnit, zda server může používat zámky stránek a zámky řádků.

--povolí zámky stránek
ALTER INDEX [pk_tabule] ON [dbo].[tabule] SET ( ALLOW_PAGE_LOCKS  = ON )

--povolí zámky řádků
ALTER INDEX [pk_tabule] ON [dbo].[tabule] SET ( ALLOW_ROW_LOCKS  = ON )

--zakáže zámky stránek
ALTER INDEX [pk_tabule] ON [dbo].[tabule] SET ( ALLOW_PAGE_LOCKS  = OFF )

--zakáže zámky řádků
ALTER INDEX [pk_tabule] ON [dbo].[tabule] SET ( ALLOW_ROW_LOCKS  = OFF )

Deadlock, zámky a indexy

Nasimulujume deadlock.
Nejdříve si vytvoříme tabulku s clusterovaným indexem.

CREATE TABLE tabule(
  id INT CONSTRAINT pk_tabule PRIMARY KEY IDENTITY(1,1), 
  a INT
) 

Dále v jednom okně management studia spustíme pár insertů pro naplnění tabulky.

BEGIN TRAN
INSERT INTO tabule(a) VALUES(3)
INSERT INTO tabule(a) VALUES(2)
INSERT INTO tabule(a) VALUES(1)

Transakci nepotvrdíme a proto záznamy zůstanou exkluzivně zamčené.

Ve druhém okně uděláme další insert.

BEGIN TRAN
INSERT INTO tabule(a) VALUES(4)

Zase transakci nepotvrdíme a záznam bude tudíž exkluzivně zamčený.

V prvním okně se třemi inserty ve stále běžící transakci se pokusíme smazat záznamy vyhovující podmínce.

DELETE FROM tabule WHERE a = 3

Protože na sloupci „a“ nemáme index, server se pokusí přečíst všechny záznamy (clustered index scan) a na každém záznamu se pokusí udělat UPDLOCK. Příkaz nedoběhne, protože bude čekat na uvolnění exkluzivního zámku na jednom vloženém záznamu z druhého okna.

Pokud se i ve druhém okně pokusíme mazat, pak dojde k deadlocku, protože v této situaci už ani jeden proces nemůže zámky uvolnit. Čekají na sebe navzájem = deadlock.

BEGIN TRAN
DELETE FROM tabule WHERE a = 4

Tyto problémy je možné vyřešit použitím vhodného indexu. Pokud vytvoříme index nad sloupcem „a“, pak při mazání záznamů s podmínkou proti sloupci „a“ server použije procházení indexu (index seek). Podaří se nám tedy smazat nezamčené záznamy, zamčené záznamy samozřejmě smazat nelze (vložené záznamy budou zamčené exkluzivně i na indexu a při mazání se nepovede vytvořit UPDLOCK zámek).

V obou transakcích provedeme ROLLBACK a vytvoříme index. Pak se pokusíme vkládat a mazat záznamy dle předchozího scénáře. Obojí se povede.

CREATE INDEX ix_tabule_a ON tabule(a)

Vhodným indexem tedy můžeme řešit problémy se zamykáním a deadlocky.

Zamknutí záznamu tak, aby nešel přečíst

Mějme situaci, kdy chceme uzamknout záznam tak, aby ho jiná transakce nemohla přečíst. Používáme ISOLATION LEVEL READ COMMITTED a kvůli propustnosti nechceme zamykat více záznamů než je nutné. Napadne nás použít hinty ROWLOCK a XLOCK, ale ejhle, ono to nefunguje.

Uvedu příklad. V jednom okně management studia vytvořím tabulku, naplním ji daty, započnu transakci, přečtu jeden záznam tabulky, který uzamknu, aby ho nemohl číst nikdo další. Dále spustím sp_lock a ověřím, že záznam je opravdu exkluzivně zamčený.

SET TRANSACTION ISOLATION LEVEL READ COMMITTED
CREATE TABLE tabulka(id INT CONSTRAINT PK_tabulka PRIMARY KEY IDENTITY(1,1), hodnota INT)
INSERT INTO tabulka VALUES(1)
INSERT INTO tabulka VALUES(2)
INSERT INTO tabulka VALUES(3)

BEGIN TRAN
SELECT *, %%LOCKRES%% AS Resource FROM tabulka WITH (ROWLOCK, XLOCK) WHERE id = 3
EXEC sp_lock

Potom ve druhém okně management studia přečtu z tabulky všechna data, očekávajíc, že to neprojde.

SET TRANSACTION ISOLATION LEVEL READ COMMITTED
SELECT * FROM tabulka

Ale ono to prošlo. Server totiž u ISOLATION LEVEL READ COMMITTED na zámky řádků moc nehledí. Všimne si, že záznam nebyl v jiné transakci změněn (je tedy COMMITTED) a klidně ho přečte. Pokud můžeme ovlivnit druhý SELECT, pak je řešením použít u něj hint HOLDLOCK. Pokud můžeme ovlivnit pouze první transakci, pak můžeme použít trik s UPDATE a vnutit tak serveru, že řádek se změnil a nemůže ho z jiné transakce číst.

UPDATE tabulka WITH (ROWLOCK) SET hodnota = hodnota + 0 WHERE id = 3

Pozor, SET hodnota = hodnota nestačí.

Další možností je, použít zamykání na úrovní stránky.

BEGIN TRAN
SELECT *, %%LOCKRES%% AS Resource FROM tabulka WITH (PAGLOCK, XLOCK) WHERE id = 3
EXEC sp_lock

Problém popsán taky v článku The madness of “exclusive” row locks.

Konverzní deadlock

Nasimulujeme si konverzní deadlock.

Nejdříve založíme tabulku, se kterou budeme následně pracovat.

CREATE TABLE tabule(a INT, b INT)
INSERT INTO tabule(a,b) VALUES(1,1)
INSERT INTO tabule(a,b) VALUES(2,2)
INSERT INTO tabule(a,b) VALUES(3,3)

Poté si v prvním okně management studia spustíme následující dotaz. (Řekněme, že hint HOLDLOCK používáme proto, že nechceme, aby nám jiný proces změnil čtená data pod rukama.)

BEGIN TRANSACTION
SELECT * FROM tabule WITH (HOLDLOCK)

A ten samý dotaz si spustíme i ve druhém okně management studia.

Oba dotazy doběhnou a vrátí data z tabulky. Protože jsme ale neukončili transakci a použili jsme HOLDLOCK budou oběma procesy sdíleně zamčené data tabulky.

Teď v obou oknech spustíme následující update.

UPDATE tabule SET b = 5 WHERE a= 1

V jednom okně pak dostaneme zprávu, že vznikl deadlock a proces byl vybrán jako oběť:

Msg 1205, Level 13, State 56, Line 1
Transaction (Process ID 51) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

Ve druhém okně update projde.

Dostali jsme se do situace, kdy dva procesy měly sdíleně uzamknutý jeden zdroj. Následně oba procesy chtěly nad tímto zdrojem použít update lock. Žádný z procesů nemohl použít update zámek, protože zdroj byl sdíleně zamknutý druhým procesem. Neřešitelná situace, deadlock. Server tedy vybral jednu transakci jako oběť a zrušil ji, čímž uvolnil její sdílený zámek. Druhá transakce pak mohla udělat update.

Jak předejít konverznímu deadlocku

Pokud víme, že data čtená v transakci budeme v této transakci i modifikovat, pak můžeme konverznímu deadlocku předejít pomocí hintu UPDLOCK.

Pokud spustíme v prvním okně tento dotaz

BEGIN TRANSACTION
SELECT * FROM tabule WITH (UPDLOCK)

pak nám doběhne a vrátí data. Ve druhém okně nám ale stejný dotaz zůstane čekat, dokud neukončíme transakci v prvním okně. Druhý proces tedy bude už od začátku čekat a nebude mít možnost uvalit na data žádné zámky. K deadlocku tedy již nemůže dojít.

Query hints a zamykání

HOLDLOCK
Použité zámky jsou drženy až do konce transakce. Obdobné jako SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, akorát jen pro jednu tabulku.
UPDLOCK
Nutí server použít při čtení update lock namísto share lock. Můžeme použít pro eliminaci konverzních deadlocků.
TABLOCK
Nutí server použít share lock na celou tabulku i přesto, že by se jinak zamykala pouze stránka. To je užitečné, pokud víme, že by se zámky nakonec eskalovaly na celou tabulku. Pokud použijeme TABLOCK při mazání z haldy, umožníme SQL serveru uvolnit stránky hned, jakmile jsou smazány.
PAGLOCK
Nutí server použít sdílený zámek na stránku, tam kde by jinak použil sdílený zámek celé tabulky.
TABLOCKX
Exkluzivní zámek tabulky držený do konce transakce. (TABLOCK a XLOCK)
ROWLOCK
Použije se sdílený zámek řádků tam, kde by se jinak použil zámek stránky nebo tabulky.
READUNCOMMITTED, REPEATABLEREAD, SERIALIZABLE
Na tabulku se použije stejný mechanizmus zamykání, jako by byl zapnutý ISOLATION LEVEL se stejným názvem.
READCOMMITTED
Pokud je READ_COMMITTED_SNAPSHOT OFF, pak server používá sdílené zámky a uvolňuje je hned jak je to možné.

Pokud je READ_COMMITTED_SNAPSHOT ON, pak server nepoužívá zámky, ale verzování řádků.
READCOMMITTEDLOCK
Server používá sdílené zámky i pokud je READ_COMMITTED_SNAPSHOT ON.
NOLOCK
Umožnuje nepotvrzené, špinavé čtení. Zámky se nehlídají, je možné přečíst data, která jsou exkluzivně zamčená. Odpovídá READUNCOMMITED.
READPAST
Přeskakuje zamčené řádky. Aplikuje se pouze při READ COMMITTED isolation level a přeskakuje pouze zámky na úrovni řádků.
XLOCK
Použije exkluzivní zámky. Je možné kombinovat s PAGLOCK nebo TABLOCK.

Zámky

Zkratka Druh zámku Popis
S Shared Umožňuje ostatním procesům číst, ale neumožňuje měnit zamčené zdroje.
Tyto zámky jsou používány automaticky, když server čte data. Mohou být použíté na tabulku, stránku, klíč indexu, nebo řádek. Více procesů může použít sdílený zámek na stejný zdroj. Na zdroj zamčený sdíleným zámkem nelze použít exkluzivní zámek. Běžně jsou zámky uvolňovány jakmile jsou data přečtena. Toto můžeme ovlivnit hintem, nebo nastavením úrovně izolace.
X Exclusive Brání ostatním měnit i číst zamčená data. Sql server tento zámek používá při INSERT, UPDATE, DELETE operacích. Data jsou zamčená po celou dobu transakce, tedy než se provede COMMIT, nebo ROLLBACK. Na takto zamčená data nelze použít žádný jiný zámek. Ostatní procesy, tak nemají k datům přístup. Toto je možné změnit pomocí hintů.
U Update Brání ostatním získat update nebo exclusive zámek. Získání shared zámku a čtení je umožněno. SQL server používá tento zámek, když vyhledává data pro modifikaci. Použitím hintů můžeme tento zámek vynutit a předejít tak konvezním deadlockům.
IS Intent shared Označuje, že komponenta zdroje je zamčena shared zámkem. Může zamykat tabulku nebo stránku.
IU Intend update Označuje, že komponenta zdroje je zamčena update zámkem. Může zamykat tabulku nebo stránku.
IX Intent exclusive Označuje, že komponenta zdroje je zamčena exclusive zámkem. Může zamykat tabulku nebo stránku.
SIX Shared with intent exclusive Označuje, že zdroj zamčený shared zámkem obsahuje komponentu (stránku nebo řádek) zamčenou exclusive zámkem.
SIU Shared with intent update Označuje, že zdoj zamčený shared zámkem obsahuje komponentu (stránku nebo řádek) zamčenou update zámkem.
UIX Update with intent exclusive Označuje, že zdoj zamčený update zámkem obsahuje komponentu (stránku nebo řádek) zamčenou update zámkem.
Sch-S Schema stability Označuje, že tabulka je kompilována.
Sch-M Schema modification Označuje, že se mění struktura tabulky.
BU Bulk update Používá se při bulk operacích a zamyká celou tabulku.