Uno dei deadlock meno comuni è quello in cui è presente un singolo utente e si blocca su alcune risorse di sistema. Uno di recente che ho riscontrato è la creazione di un tipo di alias, quindi la dichiarazione di una variabile di quel tipo, all'interno della stessa transazione. Immagina di provare a eseguire uno unit test o un test di pre-distribuzione, verificare la presenza di errori e comunque eseguire il rollback in modo da non lasciare traccia di ciò che avevi fatto. Lo schema potrebbe assomigliare a questo:
BEGIN TRANSACTION; GO CREATE TYPE EmailAddress FROM VARCHAR(320); GO DECLARE @x TABLE (e EmailAddress); GO ROLLBACK TRANSACTION;
O, più probabilmente, un po' più complesso:
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;
Il primo posto in cui ho provato questo codice è stato SQL Server 2012 ed entrambi gli esempi hanno avuto esito negativo con il seguente errore:
Msg 1205, livello 13, stato 55, riga 14La transazione (ID processo 57) è stata bloccata su risorse di blocco con un altro processo ed è stata scelta come vittima del deadlock. Eseguire nuovamente la transazione.
E non c'è molto da imparare dal grafico dello stallo:
Tornando indietro di alcuni anni, ricordo quando ho appreso per la prima volta i tipi di alias, in SQL Server 2000 (quando erano chiamati tipi di dati definiti dall'utente). A quel tempo, questo deadlock in cui mi sono imbattuto più di recente non si sarebbe verificato (ma questo è almeno in parte dovuto al fatto che non potevi dichiarare una variabile di tabella con un tipo di alias – vedi qui e qui). Ho eseguito il codice seguente su SQL Server 2000 RTM (8.0.194) e SQL Server 2000 SP4 (8.0.2039) e ha funzionato perfettamente:
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;
Naturalmente, questo scenario non era molto diffuso all'epoca perché, dopotutto, non molte persone usavano i tipi di alias in primo luogo. Sebbene possano rendere i tuoi metadati più autodocumentanti e simili alla definizione dei dati, sono una vera seccatura se vuoi cambiarli, il che potrebbe essere un argomento per un altro post.
SQL Server 2005 è arrivato e ha introdotto la nuova sintassi DDL per la creazione di tipi di alias:CREATE TYPE
. Questo non ha davvero risolto il problema con la modifica dei tipi, ha solo reso la sintassi un po' più pulita. In RTM, tutti gli esempi di codice precedenti hanno funzionato perfettamente senza deadlock. In SP4, tuttavia, si sarebbero tutti bloccati. Pertanto, da qualche parte tra RTM e SP4, hanno modificato la gestione interna per le transazioni che coinvolgevano variabili di tabella utilizzando tipi di alias.
Avanti veloce di alcuni anni a SQL Server 2008, in cui sono stati aggiunti parametri con valori di tabella (vedi un buon caso d'uso qui). Ciò ha reso l'uso di questi tipi molto più diffuso e ha introdotto un altro caso in cui una transazione che ha tentato di creare e utilizzare un tipo di questo tipo si sarebbe bloccata:
BEGIN TRANSACTION; GO CREATE TYPE dbo.Items AS TABLE(Item INT); GO DECLARE @r dbo.Items; GO ROLLBACK TRANSACTION;
Ho controllato Connect e ho trovato diversi elementi correlati, uno dei quali affermava che questo problema è stato risolto in SQL Server 2008 SP2 e 2008 R2 SP1:
Connect #365876 :si verifica un deadlock durante la creazione del tipo di dati definito dall'utente e degli oggetti che lo utilizzano
Ciò a cui si riferiva effettivamente era lo scenario seguente, in cui la semplice creazione di una stored procedure che faceva riferimento al tipo in una variabile di tabella avrebbe provocato un deadlock in SQL Server 2008 RTM (10.0.1600) e 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;
Tuttavia, questo non si blocca in SQL Server 2008 SP3 (10.0.5846) o 2008 R2 SP2 (10.50.4295). Quindi tendo a credere ai commenti sull'elemento Connect:che questa parte del bug è stata corretta nel 2008 SP2 e 2008 R2 SP1 e non è mai stato un problema nelle versioni più moderne.
Ma questo esclude comunque la possibilità di sottoporre effettivamente il tipo di alias a qualsiasi tipo di vero test. Quindi i miei unit test avrebbero avuto successo purché tutto ciò che volevo fare fosse verificare che potevo creare la procedura, dimenticando di dichiarare il tipo come variabile locale o come colonna in una variabile di tabella locale.
L'unico modo per risolvere questo problema è creare il tipo di tabella prima di iniziare la transazione e rilasciarlo esplicitamente in seguito (o dividerlo in più transazioni). Potrebbe essere estremamente ingombrante, o addirittura impossibile, avere spesso framework e imbracature di test automatizzati che cambiano completamente il modo in cui operano per tenere conto di questa limitazione.
Quindi ho deciso di eseguire alcuni test nelle build iniziali e più recenti di tutte le versioni principali:SQL Server 2005 RTM, 2005 SP4, 2008 RTM, 2008 SP3, 2008 R2 RTM, 2008 R2 SP2, 2012 RTM, 2012 SP1, e 2014 CTP2 (e sì, li ho tutti installati). Avevo esaminato diversi elementi Connect e vari commenti che mi hanno lasciato chiedendomi quali casi d'uso fossero supportati e dove, e ho avuto una strana pulsione a scoprire quali aspetti di questo problema erano stati effettivamente risolti. Ho testato vari potenziali scenari di deadlock che coinvolgono tipi di alias, variabili di tabella e parametri con valori di tabella rispetto a tutte queste build; il codice è il seguente:
/* 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;
E i risultati riflettono la mia storia sopra:SQL Server 2005 RTM non ha avuto un deadlock in nessuno degli scenari, ma quando SP4 è stato implementato, tutti si sono bloccati. Questo è stato corretto per lo scenario "crea un tipo e crea una procedura", ma nessuno degli altri, nel 2008 SP2 e 2008 R2 SP1. Ecco una tabella che mostra tutti i risultati:
Versione SQL Server/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 | ||
dichiara nella tabella var | ||||||||||
crea procedura | ||||||||||
crea ed esegui proc | ||||||||||
dichiara var locale | N/D | |||||||||
crea procedura | ||||||||||
crea ed esegui proc |
Conclusione
Quindi, la morale della storia è che non c'è ancora alcuna soluzione per il caso d'uso descritto sopra, in cui si desidera creare un tipo di tabella, creare una procedura o una funzione che utilizzi il tipo, dichiarare un tipo, testare il modulo e tirare tutto indietro. In ogni caso, ecco gli altri elementi Connect da guardare; si spera che tu possa votarli e lasciare commenti che descrivono in che modo questo scenario di stallo influisce direttamente sulla tua attività:
Mi aspetto che alcuni chiarimenti vengano aggiunti a questi elementi Connect nel prossimo futuro, anche se non so esattamente quando verranno inoltrati.