Jedno z méně běžných uváznutí je takové, kdy existuje jeden uživatel a uvízne na nějakém systémovém prostředku. Nedávno jsem narazil na vytvoření typu aliasu a následné deklarování proměnné tohoto typu uvnitř stejné transakce. Představte si, že se pokoušíte spustit test jednotky nebo test před nasazením, zkontrolujete selhání a v každém případě vrátíte zpět, abyste nezanechali žádnou stopu po tom, co jste udělali. Vzor může vypadat takto:
BEGIN TRANSACTION; GO CREATE TYPE EmailAddress FROM VARCHAR(320); GO DECLARE @x TABLE (e EmailAddress); GO ROLLBACK TRANSACTION;
Nebo, pravděpodobněji, trochu složitější:
BEGIN TRANSACTION; GO CREATE TYPE EmailAddress FROM VARCHAR(320); GO CREATE PROCEDURE dbo.foo @param EmailAddress AS BEGIN SET NOCOUNT ON; DECLARE @x TABLE (e EmailAddress); INSERT @x SELECT @param; END GO DECLARE @x EmailAddress; SET @x = N'whatever'; EXEC dbo.foo @param = N'whatever'; GO ROLLBACK TRANSACTION;
První místo, kde jsem vyzkoušel tento kód, byl SQL Server 2012 a oba příklady se nezdařily s následující chybou:
Zpráva 1205, úroveň 13, stav 55, řádek 14Transakce (ID procesu 57) uvízla na prostředcích zámku s jiným procesem a byla vybrána jako oběť zablokování. Spusťte transakci znovu.
A z grafu uváznutí se toho nedá moc naučit:
Když jsem se vrátil o několik let zpět, vzpomínám si, když jsem se poprvé dozvěděl o typech aliasů, zpět na SQL Server 2000 (kdy se jim říkalo User-Defined Data Types). V té době by k tomuto zablokování, na které jsem narazil nedávno, nedošlo (ale je to alespoň částečně proto, že jste nemohli deklarovat proměnnou tabulky s typem aliasu – viz zde a zde). Spustil jsem následující kód na SQL Server 2000 RTM (8.0.194) a SQL Server 2000 SP4 (8.0.2039) a běžel v pořádku:
BEGIN TRANSACTION; GO EXEC sp_addtype @typename = N'EmailAddress', @phystype = N'VARCHAR(320)'; GO CREATE PROCEDURE dbo.foo @param EmailAddress AS BEGIN SET NOCOUNT ON; SELECT @param; END GO EXEC dbo.foo @param = N'whatever'; GO DECLARE @x EmailAddress; SET @x = N'whatever'; EXEC dbo.foo @param = @x; GO ROLLBACK TRANSACTION;
Samozřejmě, že tento scénář nebyl v té době příliš rozšířený, protože koneckonců jen málo lidí zpočátku používalo aliasové typy. I když mohou vaše metadata více zdokumentovat a podobat se definici dat, jsou královskou bolestí, pokud je někdy budete chtít změnit, což může být téma pro další příspěvek.
Přišel SQL Server 2005 a zavedl novou syntaxi DDL pro vytváření typů aliasů:CREATE TYPE
. To ve skutečnosti nevyřešilo problém se změnou typů, jen to trochu zpřehlednilo syntaxi. V RTM všechny výše uvedené ukázky kódu fungovaly dobře bez zablokování. V SP4 by se však všechny zablokovaly. Proto někde mezi RTM a SP4 změnili interní zpracování transakcí, které zahrnovaly proměnné tabulky pomocí typů aliasů.
Rychle vpřed o několik let na SQL Server 2008, kde byly přidány parametry s hodnotou tabulky (viz příklad dobrého použití zde). Díky tomu bylo používání těchto typů mnohem rozšířenější a zavedlo to další případ, kdy by transakce, která se pokusila vytvořit a použít takový typ, uvázla na mrtvém bodě:
BEGIN TRANSACTION; GO CREATE TYPE dbo.Items AS TABLE(Item INT); GO DECLARE @r dbo.Items; GO ROLLBACK TRANSACTION;
Zkontroloval jsem Connect a našel jsem několik souvisejících položek, jedna z nich tvrdí, že tento problém byl opraven v SQL Server 2008 SP2 a 2008 R2 SP1:
Connect #365876 :Zablokování nastává při vytváření uživatelem definovaného datového typu a objektů, které jej používají
Ve skutečnosti se to týkalo následujícího scénáře, kde by jednoduché vytvoření uložené procedury odkazující na typ v proměnné tabulky uvázlo v SQL Server 2008 RTM (10.0.1600) a SQL Server 2008 R2 RTM (10.50.1600):
BEGIN TRANSACTION; GO CREATE TYPE EmailAddress FROM VARCHAR(320); GO CREATE PROCEDURE dbo.foo @param EmailAddress AS BEGIN SET NOCOUNT ON; DECLARE @x TABLE (e EmailAddress); INSERT @x SELECT @param; END GO ROLLBACK TRANSACTION;
To však nezablokuje SQL Server 2008 SP3 (10.0.5846) nebo 2008 R2 SP2 (10.50.4295). Takže mám tendenci věřit komentářům k položce Connect – že tato část chyby byla opravena v 2008 SP2 a 2008 R2 SP1 a nikdy nebyla problémem v modernějších verzích.
To však stále vynechává možnost skutečně podrobit typ aliasu jakémukoli skutečnému testování. Takže mé jednotkové testy by uspěly, dokud jsem chtěl pouze otestovat, že dokážu vytvořit proceduru – zapomeňte na deklarování typu jako lokální proměnné nebo jako sloupce v lokální proměnné tabulky.
Jediným způsobem, jak to vyřešit, je vytvořit typ tabulky před zahájením transakce a poté ji explicitně vypustit (nebo ji jinak rozdělit do více transakcí). To by mohlo být extrémně těžkopádné, nebo dokonce nemožné, aby často automatizované testovací rámce a svazky zcela změnily způsob jejich fungování, aby zohlednily toto omezení.
Rozhodl jsem se tedy projít několika testy v počátečních a nejnovějších sestaveních všech hlavních verzí:SQL Server 2005 RTM, 2005 SP4, 2008 RTM, 2008 SP3, 2008 R2 RTM, 2008 R2 SP2, 2012 RTM, 2012 SP1, a 2014 CTP2 (a ano, mám je všechny nainstalované). Zkontroloval jsem několik položek Connect a různé komentáře, které mě nechaly přemýšlet, které případy použití jsou podporovány a kde, a měl jsem zvláštní nutkání zjistit, které aspekty tohoto problému byly skutečně opraveny. Testoval jsem různé potenciální scénáře zablokování zahrnující typy aliasů, proměnné tabulky a parametry s hodnotou tabulky proti všem těmto sestavením; kód je následující:
/* alias type - declare in local table variable always deadlocks on 2005 SP4 -> 2014, except in 2005 RTM */ BEGIN TRANSACTION; GO CREATE TYPE EmailAddress FROM VARCHAR(320) GO DECLARE @r TABLE(e EmailAddress); GO ROLLBACK TRANSACTION; /* alias type - create procedure with param & table var sometimes deadlocks - 2005 SP4, 2008 RTM & SP1, 2008 R2 RTM */ BEGIN TRANSACTION; GO CREATE TYPE EmailAddress FROM VARCHAR(320); GO CREATE PROCEDURE dbo.foo @param EmailAddress AS BEGIN SET NOCOUNT ON; DECLARE @x TABLE (e EmailAddress); INSERT @x SELECT @param; END GO ROLLBACK TRANSACTION; /* alias type - create procedure, declare & exec always deadlocks on 2005 SP4 -> 2014, except on 2005 RTM */ BEGIN TRANSACTION; GO CREATE TYPE EmailAddress FROM VARCHAR(320); GO CREATE PROCEDURE dbo.foo @param EmailAddress AS BEGIN SET NOCOUNT ON; DECLARE @x TABLE (e EmailAddress); INSERT @x SELECT @param; END GO DECLARE @x EmailAddress; SET @x = N'whatever'; EXEC dbo.foo @param = N'whatever'; GO ROLLBACK TRANSACTION; /* obviously did not run these on SQL Server 2005 builds */ /* table type - create & declare local variable always deadlocks on 2008 -> 2014 */ BEGIN TRANSACTION; GO CREATE TYPE dbo.Items AS TABLE(Item INT); GO DECLARE @r dbo.Items; GO ROLLBACK TRANSACTION; /* table type - create procedure with param and SELECT never deadlocks on 2008 -> 2014 */ BEGIN TRANSACTION; GO CREATE TYPE dbo.Items AS TABLE(Item INT); GO CREATE PROCEDURE dbo.foo @param dbo.Items READONLY AS BEGIN SET NOCOUNT ON; SELECT Item FROM @param; END GO ROLLBACK TRANSACTION; /* table type - create procedure, declare & exec always deadlocks on 2008 -> 2014 */ BEGIN TRANSACTION; GO CREATE TYPE dbo.Items AS TABLE(Item INT); GO CREATE PROCEDURE dbo.foo @param dbo.Items READONLY AS BEGIN SET NOCOUNT ON; SELECT Item FROM @param; END GO DECLARE @x dbo.Items; EXEC dbo.foo @param = @x; GO ROLLBACK TRANSACTION;
A výsledky odrážejí můj příběh výše:SQL Server 2005 RTM neuvízl na mrtvém bodě v žádném ze scénářů, ale v době, kdy se objevil SP4, uvázly všechny. To bylo opraveno pro scénář "vytvořit typ a vytvořit postup", ale žádný z ostatních, v roce 2008 SP2 a 2008 R2 SP1. Zde je tabulka se všemi výsledky:
Verze serveru SQL / sestavení # | ||||||||||
SQL 2005 | SQL 2008 | SQL 2008 R2 | SQL 2012 | SQL 2014 | ||||||
RTM 9.0.1399 | SP4 9.0.5324 | RTM 10.0.1600 | SP3 10.0.5846 | RTM 10 50 1600 | SP2 10.50.4295 | RTM 11.0.2100 | SP1 11.0.3381 | CTP2 12.0.1524 | ||
deklarujte v tabulce var | ||||||||||
vytvořit postup | ||||||||||
vytvořit a spustit proc | ||||||||||
deklarovat místní var | Není k dispozici | |||||||||
vytvořit postup | ||||||||||
vytvořit a spustit proc |
Závěr
Takže morálka příběhu je taková, že stále neexistuje žádná oprava pro případ použití popsaný výše, kde chcete vytvořit typ tabulky, vytvořit proceduru nebo funkci, která používá typ, deklarovat typ, otestovat modul a vrátit všechno zpět. V každém případě jsou zde další položky Connect, na které se můžete podívat; doufám, že pro ně můžete hlasovat a zanechat komentáře popisující, jak tento scénář zablokování přímo ovlivňuje vaši firmu:
Očekávám, že v blízké budoucnosti budou k těmto položkám Connectu přidána některá upřesnění, i když přesně nevím, kdy budou prosazeny.