sql >> Databasteknik >  >> RDS >> Sqlserver

Efter ett dödläge med en transaktion över SQL Server-versioner

En av de mindre vanliga låsningarna är en där det finns en enda användare och de låser sig själva på någon systemresurs. En ny som jag stötte på är att skapa en aliastyp och sedan deklarera en variabel av den typen i samma transaktion. Föreställ dig att du försöker köra ett enhetstest eller ett pre-installationstest, kontrollera efter misslyckanden och återställa i alla fall så att du inte lämnar några spår av vad du har gjort. Mönstret kan se ut så här:

BEGIN TRANSACTION;
GO
CREATE TYPE EmailAddress FROM VARCHAR(320);
GO
DECLARE @x TABLE (e EmailAddress);
GO
ROLLBACK TRANSACTION;

Eller, mer troligt, lite mer komplex:

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örsta jag försökte med den här koden var SQL Server 2012, och båda exemplen misslyckades med följande fel:

Msg 1205, Level 13, State 55, Line 14
Transaktion (Process ID 57) var låst på låsresurser med en annan process och har valts som dödlägesoffer. Kör transaktionen igen.

Och det finns inte mycket att lära från dödlägesdiagrammet:

Med ett steg tillbaka några år minns jag när jag först lärde mig om aliastyper, tillbaka i SQL Server 2000 (när de kallades användardefinierade datatyper). Vid den tiden skulle detta dödläge som jag stötte på mer nyligen inte inträffa (men det beror åtminstone delvis på att du inte kunde deklarera en tabellvariabel med en aliastyp – se här och här). Jag körde följande kod på SQL Server 2000 RTM (8.0.194) och SQL Server 2000 SP4 (8.0.2039), och det gick bra:

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;

Naturligtvis var detta scenario inte särskilt utbrett vid den tiden eftersom det trots allt inte var många som använde aliastyper i första hand. Även om de kan göra din metadata mer självdokumenterande och datadefinitionslik, är de en kunglig smärta om du någonsin vill ändra dem, vilket kan vara ett ämne för ett annat inlägg.

SQL Server 2005 kom och introducerade ny DDL-syntax för att skapa aliastyper:CREATE TYPE . Detta löste inte riktigt problemet med att byta typer, det gjorde bara syntaxen lite renare. I RTM fungerade alla ovanstående kodexempel alldeles utmärkt utan dödläge. I SP4 skulle de dock alla låsa sig. Därför ändrade de, någonstans mellan RTM och SP4, den interna hanteringen för transaktioner som involverade tabellvariabler med hjälp av aliastyper.

Spola framåt några år till SQL Server 2008, där tabellvärdade parametrar lades till (se ett bra användningsfall här). Detta gjorde användningen av dessa typer mycket vanligare, och introducerade ett annat fall där en transaktion som försökte skapa och använda en sådan typ skulle låsa sig:

BEGIN TRANSACTION;
GO
CREATE TYPE dbo.Items AS TABLE(Item INT);
GO
DECLARE @r dbo.Items;
GO
ROLLBACK TRANSACTION;

Jag kollade Connect och hittade flera relaterade objekt, en av dem hävdade att det här problemet har åtgärdats i SQL Server 2008 SP2 och 2008 R2 SP1:

Anslut #365876 :dödläge uppstår när användardefinierad datatyp och objekt som använder den skapas

Vad detta faktiskt syftade på var följande scenario, där att helt enkelt skapa en lagrad procedur som refererade till typen i en tabellvariabel skulle låsa fast i SQL Server 2008 RTM (10.0.1600) och 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;

Detta låser dock inte i SQL Server 2008 SP3 (10.0.5846) eller 2008 R2 SP2 (10.50.4295). Så jag tenderar att tro på kommentarerna om Connect-objektet – att denna del av felet fixades under 2008 SP2 och 2008 R2 SP1, och att det aldrig har varit ett problem i modernare versioner.

Men detta utelämnar fortfarande möjligheten att faktiskt sätta aliastypen genom någon form av sann testning. Så mina enhetstester skulle lyckas så länge allt jag ville göra var att testa att jag kunde skapa proceduren – glöm bort att deklarera typen som en lokal variabel eller som en kolumn i en lokal tabellvariabel.

Det enda sättet att lösa detta är att skapa tabelltypen innan transaktionen påbörjas, och uttryckligen släppa den efteråt (eller på annat sätt dela upp den i flera transaktioner). Detta kan vara extremt besvärligt, eller till och med omöjligt, att ofta automatiserade testramar och selar helt förändrar hur de fungerar för att ta hänsyn till denna begränsning.

Så jag bestämde mig för att gå igenom några tester i de första och senaste versionerna av alla större versioner:SQL Server 2005 RTM, 2005 SP4, 2008 RTM, 2008 SP3, 2008 R2 RTM, 2008 R2 SP2, 2012 RTM, 2012 SP1, och 2014 CTP2 (och ja, jag har alla installerade). Jag hade granskat flera Connect-objekt och olika kommentarer som fick mig att undra vilka användningsfall som stöddes och var, och jag hade ett konstigt tvång att ta reda på vilka aspekter av det här problemet som faktiskt hade åtgärdats. Jag testade olika potentiella dödlägesscenarier som involverade aliastyper, tabellvariabler och tabellvärderade parametrar mot alla dessa builds; koden är följande:

/* 
  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;

Och resultaten speglar min historia ovan:SQL Server 2005 RTM låste sig inte i något av scenarierna, men när SP4 rullade runt hade de alla låst sig. Detta korrigerades för scenariot "skapa en typ och skapa en procedur", men ingen av de andra, 2008 SP2 och 2008 R2 SP1. Här är en tabell som visar alla resultat:

SQL-serverversion/byggnr.
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
Aliastyp deklarera i tabell var
skapa procedur
skapa och utför proc
Tabelltyp deklarera lokal var N/A
skapa procedur
skapa och utför proc

Slutsats

Så, moralen i historien är att det fortfarande inte finns någon fix för användningsfallet som beskrivs ovan, där du vill skapa en tabelltyp, skapa en procedur eller funktion som använder typen, deklarera en typ, testa modulen och rulla allt tillbaka. Hur som helst, här är de andra Connect-objekten för dig att titta på; förhoppningsvis kan du rösta på dem och lämna kommentarer som beskriver hur detta dödlägesscenario direkt påverkar ditt företag:

  • Anslut #581193 :Att skapa en tabelltyp och använda den i samma transaktion orsakar ett dödläge
  • Anslut #800919 :Problem med att skapa en funktion med TableValue Return Skriv in transaktion med användardefinierad typ i tabell som skapas i samma transaktionsomfång
  • Anslut #804365 :Deadlock uppstår när en användardefinierad tabelltyp skapas och används i en transaktion

    Jag förväntar mig att vissa förtydliganden kommer att läggas till dessa Connect-objekt inom en snar framtid, även om jag inte vet exakt när de kommer att drivas igenom.


    1. PostgreSQL-logganalys med pgBadger

    2. 5 SQL-syntax och frågeprinciper för bättre databasövervakning

    3. Oracle - Hur man skapar en skrivskyddad användare

    4. SSIS misslyckas med att spara paket och startar om Visual Studio