Jednym z mniej powszechnych zakleszczeń jest taki, w którym występuje jeden użytkownik, który blokuje się na jakimś zasobie systemowym. Ostatnio natknąłem się na utworzenie typu aliasu, a następnie zadeklarowanie zmiennej tego typu w tej samej transakcji. Wyobraź sobie, że próbujesz uruchomić test jednostkowy lub test przed wdrożeniem, sprawdzić błędy i cofnąć w każdym przypadku, aby nie pozostawić żadnego śladu po tym, co zrobiłeś. Wzór może wyglądać tak:
BEGIN TRANSACTION; GO CREATE TYPE EmailAddress FROM VARCHAR(320); GO DECLARE @x TABLE (e EmailAddress); GO ROLLBACK TRANSACTION;
Lub, co bardziej prawdopodobne, trochę bardziej złożone:
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;
Pierwszym miejscem, w którym wypróbowałem ten kod, był SQL Server 2012, a oba przykłady nie powiodły się z następującym błędem:
Msg 1205, poziom 13, stan 55, wiersz 14Transakcja (identyfikator procesu 57) została zablokowana w zasobach blokady z innym procesem i została wybrana jako ofiara zakleszczenia. Ponownie uruchom transakcję.
A z wykresu impasu nie można się wiele nauczyć:
Cofając się o kilka lat, przypominam sobie, kiedy po raz pierwszy dowiedziałem się o typach aliasów, w SQL Server 2000 (kiedy nazywano je typami danych definiowanymi przez użytkownika). W tamtym czasie ten impas, na który natknąłem się ostatnio, nie miałby miejsca (ale przynajmniej częściowo dlatego, że nie można było zadeklarować zmiennej tabeli z typem aliasu – zobacz tutaj i tutaj). Uruchomiłem następujący kod na SQL Server 2000 RTM (8.0.194) i SQL Server 2000 SP4 (8.0.2039) i działał dobrze:
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;
Oczywiście ten scenariusz nie był wtedy zbyt rozpowszechniony, ponieważ w końcu niewiele osób używało typów aliasów. Chociaż mogą sprawić, że Twoje metadane będą bardziej samodokumentujące i podobne do definicji danych, są one prawdziwym problemem, jeśli kiedykolwiek zechcesz je zmienić, co może być tematem na inny post.
Pojawił się SQL Server 2005 i wprowadził nową składnię DDL do tworzenia typów aliasów:CREATE TYPE
. To tak naprawdę nie rozwiązało problemu ze zmianą typów, po prostu sprawiło, że składnia była trochę czystsza. W RTM wszystkie powyższe próbki kodu działały dobrze bez zakleszczeń. Jednak w SP4 wszyscy utknęliby w martwym punkcie. Dlatego gdzieś pomiędzy RTM a SP4 zmieniono wewnętrzną obsługę transakcji, które obejmowały zmienne tabel przy użyciu typów aliasów.
Przewiń o kilka lat do SQL Server 2008, gdzie dodano parametry wyceniane w tabeli (zobacz tutaj dobry przypadek użycia). To sprawiło, że korzystanie z tych typów stało się znacznie bardziej rozpowszechnione i wprowadziło inny przypadek, w którym transakcja, która próbowała utworzyć i użyć takiego typu, byłaby zakleszczona:
BEGIN TRANSACTION; GO CREATE TYPE dbo.Items AS TABLE(Item INT); GO DECLARE @r dbo.Items; GO ROLLBACK TRANSACTION;
Sprawdziłem Connect i znalazłem kilka powiązanych elementów, z których jeden twierdził, że ten problem został naprawiony w SQL Server 2008 SP2 i 2008 R2 SP1:
Połącz #365876 :Zakleszczenie występuje podczas tworzenia typu danych zdefiniowanego przez użytkownika i obiektów, które go używają
W rzeczywistości odnosiło się to do następującego scenariusza, w którym zwykłe utworzenie procedury składowanej, która odwołuje się do typu w zmiennej tabeli, spowodowałoby zakleszczenie w SQL Server 2008 RTM (10.0.1600) i 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;
Jednak nie powoduje to zakleszczenia w dodatku SP3 dla programu SQL Server 2008 (10.0.5846) lub 2008 R2 z dodatkiem SP2 (10.50.4295). Dlatego wierzę w komentarze dotyczące elementu Connect – że ta część błędu została naprawiona w 2008 SP2 i 2008 R2 SP1 i nigdy nie stanowiła problemu w nowszych wersjach.
Ale to nadal wyklucza możliwość poddania typu aliasu jakimkolwiek prawdziwym testom. Więc moje testy jednostkowe powiodą się tak długo, jak długo chciałem tylko przetestować, czy mogę stworzyć procedurę – zapomnij o deklarowaniu typu jako zmiennej lokalnej lub jako kolumny w lokalnej zmiennej tabeli.
Jedynym sposobem rozwiązania tego problemu jest utworzenie typu tabeli przed rozpoczęciem transakcji i jawne porzucenie go później (lub w inny sposób rozbicie go na wiele transakcji). Może to być niezwykle uciążliwe, a nawet niemożliwe, aby często zautomatyzowane struktury testowe i narzędzia całkowicie zmieniały sposób ich działania, aby uwzględnić to ograniczenie.
Postanowiłem więc przeprowadzić kilka testów w początkowych i najnowszych kompilacjach wszystkich głównych wersji:SQL Server 2005 RTM, 2005 SP4, 2008 RTM, 2008 SP3, 2008 R2 RTM, 2008 R2 SP2, 2012 RTM, 2012 SP1, i 2014 CTP2 (i tak, mam je wszystkie zainstalowane). Przejrzałem kilka elementów Connect i różne komentarze, które sprawiły, że zacząłem się zastanawiać, które przypadki użycia są obsługiwane i gdzie, i poczułem dziwną potrzebę sprawdzenia, które aspekty tego problemu zostały faktycznie naprawione. Przetestowałem różne potencjalne scenariusze zakleszczenia obejmujące typy aliasów, zmienne tabel i parametry wyceniane w tabeli względem wszystkich tych kompilacji; kod wygląda następująco:
/* 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;
Wyniki odzwierciedlają moją powyższą historię:SQL Server 2005 RTM nie zablokował się w żadnym ze scenariuszy, ale do czasu wprowadzenia dodatku SP4 wszystkie one się zacięły. Zostało to poprawione dla scenariusza „Utwórz typ i utwórz procedurę”, ale żaden z pozostałych w wersjach 2008 SP2 i 2008 R2 SP1. Oto tabela przedstawiająca wszystkie wyniki:
Wersja / kompilacja serwera SQL | ||||||||||
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 | ||
deklaruj w tabeli var | ||||||||||
utwórz procedurę | ||||||||||
utwórz i wykonaj proc | ||||||||||
deklaruj lokalną odmianę | Nie dotyczy | |||||||||
utwórz procedurę | ||||||||||
utwórz i wykonaj proc |
Wniosek
Morał tej historii jest taki, że nadal nie ma rozwiązania dla opisanego powyżej przypadku użycia, w którym chcesz utworzyć typ tabeli, utworzyć procedurę lub funkcję korzystającą z tego typu, zadeklarować typ, przetestować moduł i wykonać rzut wszystko z powrotem. W każdym razie, oto inne elementy Connect, na które możesz się przyjrzeć; miejmy nadzieję, że będziesz mógł na nie zagłosować i zostawić komentarze opisujące, w jaki sposób ten impas wpływa bezpośrednio na Twoją firmę:
Spodziewam się w niedalekiej przyszłości pewnych wyjaśnień do tych elementów Connect, chociaż nie wiem dokładnie, kiedy zostaną przeforsowane.