Een van de minder vaak voorkomende deadlocks is er een waarbij er een enkele gebruiker is en ze zichzelf vastzetten op een systeembron. Een recente die ik tegenkwam, is het maken van een aliastype en vervolgens het declareren van een variabele van dat type, binnen dezelfde transactie. Stel je voor dat je een unit-test of pre-deployment test probeert uit te voeren, te controleren op fouten en in ieder geval terug te draaien, zodat je geen spoor achterlaat van wat je hebt gedaan. Het patroon kan er als volgt uitzien:
BEGIN TRANSACTION; GO CREATE TYPE EmailAddress FROM VARCHAR(320); GO DECLARE @x TABLE (e EmailAddress); GO ROLLBACK TRANSACTION;
Of, waarschijnlijker, een beetje ingewikkelder:
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;
De eerste plaats waar ik deze code probeerde was SQL Server 2012, en beide voorbeelden mislukten met de volgende fout:
Msg 1205, Level 13, State 55, Line 14Transactie (proces-ID 57) liep vast op lock-bronnen met een ander proces en is gekozen als het deadlock-slachtoffer. Voer de transactie opnieuw uit.
En er valt niet veel te leren van de impassegrafiek:
Als ik een paar jaar terug ben, herinner ik me dat ik voor het eerst hoorde over aliastypen, in SQL Server 2000 (toen ze door de gebruiker gedefinieerde gegevenstypen werden genoemd). Op dat moment zou deze impasse die ik recentelijk tegenkwam niet plaatsvinden (maar dit is tenminste gedeeltelijk omdat je een tabelvariabele met een aliastype niet kon declareren - zie hier en hier). Ik heb de volgende code uitgevoerd op SQL Server 2000 RTM (8.0.194) en SQL Server 2000 SP4 (8.0.2039) en het werkte prima:
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;
Natuurlijk was dit scenario destijds niet erg wijdverbreid, want in de eerste plaats gebruikten niet veel mensen aliastypes. Hoewel ze je metadata misschien meer zelfdocumenterend en data-definition-achtig maken, zijn ze een koninklijke pijn als je ze ooit wilt veranderen, wat een onderwerp kan zijn voor een ander bericht.
SQL Server 2005 kwam rond en introduceerde nieuwe DDL-syntaxis voor het maken van aliastypen:CREATE TYPE
. Dit loste het probleem met het wijzigen van de typen niet echt op, het maakte de syntaxis alleen een beetje schoner. In RTM werkten alle bovenstaande codevoorbeelden prima zonder deadlocks. In SP4 zouden ze echter allemaal in een impasse raken. Daarom hebben ze ergens tussen RTM en SP4 de interne afhandeling gewijzigd voor transacties waarbij tabelvariabelen betrokken waren met behulp van aliastypen.
Snel een paar jaar vooruit naar SQL Server 2008, waar parameters met tabelwaarde werden toegevoegd (zie hier een goede use-case). Hierdoor kwam het gebruik van deze typen veel vaker voor en werd een ander geval geïntroduceerd waarin een transactie die probeerde een dergelijk type te creëren en te gebruiken, zou vastlopen:
BEGIN TRANSACTION; GO CREATE TYPE dbo.Items AS TABLE(Item INT); GO DECLARE @r dbo.Items; GO ROLLBACK TRANSACTION;
Ik controleerde Connect en vond verschillende gerelateerde items, waarvan er één beweerde dat dit probleem is opgelost in SQL Server 2008 SP2 en 2008 R2 SP1:
Connect #365876:Deadlock treedt op bij het maken van door de gebruiker gedefinieerd gegevenstype en objecten die het gebruiken
Waar dit in feite naar verwees, was het volgende scenario, waarbij het eenvoudig maken van een opgeslagen procedure die naar het type in een tabelvariabele verwijst, zou vastlopen in SQL Server 2008 RTM (10.0.1600) en 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;
Dit leidt echter niet tot een impasse in SQL Server 2008 SP3 (10.0.5846) of 2008 R2 SP2 (10.50.4295). Dus ik ben geneigd de opmerkingen over het Connect-item te geloven - dat dit deel van de bug is opgelost in 2008 SP2 en 2008 R2 SP1 en nooit een probleem is geweest in modernere versies.
Maar dit laat nog steeds de mogelijkheid weg om het aliastype daadwerkelijk door enige vorm van echte tests te laten gaan. Dus mijn unit-tests zouden slagen zolang ik alleen maar wilde testen of ik de procedure kon maken - vergeet het type als een lokale variabele of als een kolom in een lokale tabelvariabele te declareren.
De enige manier om dit op te lossen, is door het tabeltype aan te maken voordat de transactie wordt gestart en deze daarna expliciet te laten vallen (of deze op een andere manier op te splitsen in meerdere transacties). Dit kan buitengewoon omslachtig of zelfs onmogelijk zijn om vaak geautomatiseerde testkaders en harnassen de manier waarop ze werken volledig te laten veranderen om rekening te houden met deze beperking.
Dus besloot ik enkele tests uit te voeren in de eerste en meest recente builds van alle belangrijke versies:SQL Server 2005 RTM, 2005 SP4, 2008 RTM, 2008 SP3, 2008 R2 RTM, 2008 R2 SP2, 2012 RTM, 2012 SP1, en 2014 CTP2 (en ja, ik heb ze allemaal geïnstalleerd). Ik had verschillende Connect-items en verschillende opmerkingen bekeken waardoor ik me afvroeg welke use-cases werden ondersteund en waar, en ik had een vreemde dwang om erachter te komen welke aspecten van dit probleem daadwerkelijk waren opgelost. Ik heb verschillende mogelijke impassescenario's met aliastypen, tabelvariabelen en tabelwaardeparameters getest tegen al deze builds; de code is als volgt:
/* 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;
En de resultaten weerspiegelen mijn verhaal hierboven:SQL Server 2005 RTM liep in geen van de scenario's vast, maar tegen de tijd dat SP4 ronddraaide, zaten ze allemaal in een impasse. Dit is gecorrigeerd voor het scenario "maak een type en maak een procedure", maar geen van de andere, in 2008 SP2 en 2008 R2 SP1. Hier is een tabel met alle resultaten:
SQL Server-versie / Build # | ||||||||||
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 | ||
declareer in tabel var | ||||||||||
procedure aanmaken | ||||||||||
creëren &uitvoeren proc | ||||||||||
declareer lokale var | N.v.t. | |||||||||
procedure aanmaken | ||||||||||
creëren &uitvoeren proc |
Conclusie
Dus de moraal van het verhaal is dat er nog steeds geen oplossing is voor de hierboven beschreven use case, waarbij je een tabeltype wilt maken, een procedure of functie wilt maken die het type gebruikt, een type declareert, de module test en rolt alles terug. In ieder geval zijn hier de andere Connect-items om naar te kijken; hopelijk kunt u op hen stemmen en opmerkingen achterlaten waarin wordt beschreven hoe dit impassescenario uw bedrijf rechtstreeks beïnvloedt:
Ik verwacht dat er in de nabije toekomst enige verduidelijking zal worden toegevoegd aan deze Connect-items, hoewel ik niet precies weet wanneer ze zullen worden doorgevoerd.