En af de mindre almindelige deadlocks er en, hvor der er en enkelt bruger, og de låser sig selv på en systemressource. En nylig, jeg stødte på, er at oprette en aliastype og derefter erklære en variabel af den type inden for samme transaktion. Forestil dig, at du prøver at køre en enhedstest eller en test før implementering, kontrollere for fejl og under alle omstændigheder rulle tilbage, så du ikke efterlader noget spor af, hvad du har gjort. Mønsteret kan se sådan ud:
BEGIN TRANSACTION; GO CREATE TYPE EmailAddress FROM VARCHAR(320); GO DECLARE @x TABLE (e EmailAddress); GO ROLLBACK TRANSACTION;
Eller mere sandsynligt, lidt mere kompleks:
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;
Det første sted, jeg prøvede denne kode, var SQL Server 2012, og begge eksempler mislykkedes med følgende fejl:
Meddelelse 1205, niveau 13, tilstand 55, linje 14Transaktion (Proces ID 57) var låst på låseressourcer med en anden proces og er blevet valgt som offer for dødvande. Kør transaktionen igen.
Og der er slet ikke meget at lære af dødvandsgrafen:
Når jeg går et par år tilbage, husker jeg, da jeg første gang lærte om aliastyper, tilbage i SQL Server 2000 (da de blev kaldt brugerdefinerede datatyper). På det tidspunkt ville dette dødvande, som jeg stødte på for nylig, ikke ske (men det er i det mindste delvist fordi man ikke kunne erklære en tabelvariabel med en aliastype – se her og her). Jeg kørte følgende kode på SQL Server 2000 RTM (8.0.194) og SQL Server 2000 SP4 (8.0.2039), og den kørte fint:
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;
Selvfølgelig var dette scenarie ikke særlig udbredt på det tidspunkt, fordi der trods alt ikke var mange, der brugte aliastyper i første omgang. Selvom de kan gøre dine metadata mere selvdokumenterende og datadefinitionsagtige, er de en kongelig smerte, hvis du nogensinde vil ændre dem, hvilket kan være et emne for et andet indlæg.
SQL Server 2005 kom omkring og introducerede ny DDL-syntaks til at skabe aliastyper:CREATE TYPE
. Dette løste ikke rigtig problemet med at skifte typer, det gjorde bare syntaksen lidt renere. I RTM fungerede alle ovenstående kodeeksempler fint uden dødvande. I SP4 ville de dog alle gå i stå. Derfor ændrede de et sted mellem RTM og SP4 den interne håndtering af transaktioner, der involverede tabelvariabler ved hjælp af aliastyper.
Spol et par år frem til SQL Server 2008, hvor tabelværdisatte parametre blev tilføjet (se en god use case her). Dette gjorde brugen af disse typer meget mere udbredt, og introducerede et andet tilfælde, hvor en transaktion, der forsøgte at oprette og bruge en sådan type, ville gå i stå:
BEGIN TRANSACTION; GO CREATE TYPE dbo.Items AS TABLE(Item INT); GO DECLARE @r dbo.Items; GO ROLLBACK TRANSACTION;
Jeg tjekkede Connect, og fandt flere relaterede emner, hvoraf et af dem hævdede, at dette problem er blevet rettet i SQL Server 2008 SP2 og 2008 R2 SP1:
Connect #365876 :Deadlock opstår ved oprettelse af brugerdefineret datatype og objekter, der bruger den
Hvad dette faktisk refererede til, var følgende scenarie, hvor blot oprettelse af en lagret procedure, der refererede til typen i en tabelvariabel, ville låse fast i SQL Server 2008 RTM (10.0.1600) og 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;
Dette blokerer dog ikke i SQL Server 2008 SP3 (10.0.5846) eller 2008 R2 SP2 (10.50.4295). Så jeg er tilbøjelig til at tro på kommentarerne til Connect-emnet – at denne del af fejlen blev rettet i 2008 SP2 og 2008 R2 SP1 og aldrig har været et problem i mere moderne versioner.
Men dette udelader stadig muligheden for faktisk at sætte aliastypen gennem enhver form for ægte test. Så mine enhedstest ville lykkes, så længe det eneste, jeg ønskede at gøre, var at teste, at jeg kunne oprette proceduren – glem alt om at erklære typen som en lokal variabel eller som en kolonne i en lokal tabelvariabel.
Den eneste måde at løse dette på er at oprette tabeltypen, før transaktionen startes, og udtrykkeligt droppe den bagefter (eller på anden måde dele den op i flere transaktioner). Det kan være ekstremt besværligt, eller endda umuligt, at få ofte automatiserede testrammer og seler fuldstændigt at ændre den måde, de fungerer på, for at tage højde for denne begrænsning.
Så jeg besluttede at gennemgå nogle tests i de indledende og seneste builds af alle de større versioner:SQL Server 2005 RTM, 2005 SP4, 2008 RTM, 2008 SP3, 2008 R2 RTM, 2008 R2 SP2, 2012 RTM, 2012 SP1, og 2014 CTP2 (og ja, jeg har dem alle installeret). Jeg havde gennemgået adskillige Connect-elementer og forskellige kommentarer, der fik mig til at spekulere på, hvilke use cases der blev understøttet og hvor, og jeg havde en mærkelig tvang til at finde ud af, hvilke aspekter af dette problem faktisk var blevet løst. Jeg testede forskellige potentielle dødvandsscenarier, der involverede aliastyper, tabelvariabler og tabelværdisatte parametre mod alle disse builds; koden er som følger:
/* 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;
Og resultaterne afspejler min historie ovenfor:SQL Server 2005 RTM låste ikke nogen af scenarierne, men da SP4 rullede rundt, gik de alle sammen. Dette blev korrigeret for scenariet "opret en type og opret en procedure", men ingen af de andre, i 2008 SP2 og 2008 R2 SP1. Her er en tabel, der viser alle resultaterne:
SQL-serverversion/buildnr. | ||||||||||
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 | ||
erklær i tabel var | ||||||||||
opret procedure | ||||||||||
opret og udfør proc | ||||||||||
erklær lokal var | Ikke relevant | |||||||||
opret procedure | ||||||||||
opret og udfør proc |
Konklusion
Så moralen i historien er, at der stadig ikke er nogen rettelse til use casen beskrevet ovenfor, hvor du vil oprette en tabeltype, oprette en procedure eller funktion, der bruger typen, erklære en type, teste modulet og rulle alt tilbage. Under alle omstændigheder, her er de andre Connect-artikler, som du kan se på; forhåbentlig kan du stemme på dem og efterlade kommentarer, der beskriver, hvordan dette dødvande-scenarie påvirker din virksomhed direkte:
Jeg forventer fuldt ud, at der vil blive tilføjet nogle afklaringer til disse Connect-elementer i den nærmeste fremtid, selvom jeg ikke ved præcis, hvornår de bliver presset igennem.