Um dos deadlocks menos comuns é aquele em que há um único usuário e eles se bloqueiam em algum recurso do sistema. Um recente que encontrei é criar um tipo de alias e depois declarar uma variável desse tipo, dentro da mesma transação. Imagine que você está tentando executar um teste de unidade ou teste de pré-implantação, verificar falhas e reverter em qualquer caso para não deixar nenhum rastro do que você fez. O padrão pode ficar assim:
BEGIN TRANSACTION; GO CREATE TYPE EmailAddress FROM VARCHAR(320); GO DECLARE @x TABLE (e EmailAddress); GO ROLLBACK TRANSACTION;
Ou, mais provavelmente, um pouco mais complexo:
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;
O primeiro lugar em que tentei esse código foi o SQL Server 2012, e ambos os exemplos falharam com o seguinte erro:
Msg 1205, Level 13, State 55, Line 14
A transação (Process ID 57) foi bloqueada em recursos de bloqueio com outro processo e foi escolhida como vítima de deadlock. Execute novamente a transação.
E não há muito o que aprender com o gráfico de impasse:
Recuando alguns anos, lembro-me de quando aprendi pela primeira vez sobre os tipos de alias, no SQL Server 2000 (quando eram chamados de Tipos de Dados Definidos pelo Usuário). Naquela época, esse impasse que encontrei mais recentemente não aconteceria (mas isso é pelo menos em parte porque você não poderia declarar uma variável de tabela com um tipo de alias – veja aqui e aqui). Executei o seguinte código no SQL Server 2000 RTM (8.0.194) e no SQL Server 2000 SP4 (8.0.2039) e funcionou bem:
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;
Claro, esse cenário não era muito difundido na época porque, afinal, muitas pessoas não usavam tipos de alias em primeiro lugar. Embora eles possam tornar seus metadados mais autodocumentados e semelhantes à definição de dados, eles são uma dor real se você quiser alterá-los, o que pode ser um tópico para outro post.
O SQL Server 2005 surgiu e introduziu uma nova sintaxe DDL para criar tipos de alias:
CREATE TYPE
. Isso realmente não resolveu o problema de alterar os tipos, apenas tornou a sintaxe um pouco mais limpa. No RTM, todos os exemplos de código acima funcionaram bem sem deadlocks. No SP4, no entanto, todos eles travariam. Portanto, em algum lugar entre RTM e SP4, eles mudaram o tratamento interno para transações que envolviam variáveis de tabela usando tipos de alias. Avance alguns anos para o SQL Server 2008, onde os parâmetros com valor de tabela foram adicionados (veja um bom caso de uso aqui). Isso tornou o uso desses tipos muito mais prevalente e introduziu outro caso em que uma transação que tentasse criar e usar esse tipo travaria:
BEGIN TRANSACTION; GO CREATE TYPE dbo.Items AS TABLE(Item INT); GO DECLARE @r dbo.Items; GO ROLLBACK TRANSACTION;
Verifiquei o Connect e encontrei vários itens relacionados, um deles alegando que esse problema foi corrigido no SQL Server 2008 SP2 e 2008 R2 SP1:
Connect #365876 :O deadlock ocorre ao criar tipos de dados definidos pelo usuário e objetos que os utilizam
O que isso realmente se referia era o seguinte cenário, onde simplesmente criar um procedimento armazenado que referenciasse o tipo em uma variável de tabela travaria no SQL Server 2008 RTM (10.0.1600) e no 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;
No entanto, isso não trava no SQL Server 2008 SP3 (10.0.5846) ou 2008 R2 SP2 (10.50.4295). Então, eu tendo a acreditar nos comentários sobre o item Connect – que essa parte do bug foi corrigida em 2008 SP2 e 2008 R2 SP1, e nunca foi um problema em versões mais modernas.
Mas isso ainda deixa de fora a capacidade de realmente colocar o tipo de alias em qualquer tipo de teste verdadeiro. Assim, meus testes de unidade seriam bem-sucedidos desde que tudo o que eu quisesse fazer fosse testar se eu poderia criar o procedimento – esqueça de declarar o tipo como uma variável local ou como uma coluna em uma variável de tabela local.
A única maneira de resolver isso é criar o tipo de tabela antes de iniciar a transação e eliminá-lo explicitamente depois (ou dividi-lo em várias transações). Isso pode ser extremamente complicado, ou mesmo impossível, ter frameworks e equipamentos de teste frequentemente automatizados para mudar completamente a maneira como eles operam para levar em conta essa limitação.
Então resolvi fazer alguns testes nas versões iniciais e mais recentes de todas as principais versões:SQL Server 2005 RTM, 2005 SP4, 2008 RTM, 2008 SP3, 2008 R2 RTM, 2008 R2 SP2, 2012 RTM, 2012 SP1, e 2014 CTP2 (e sim, eu tenho todos eles instalados). Eu revi vários itens do Connect e vários comentários que me deixaram imaginando quais casos de uso eram suportados e onde, e eu tinha uma estranha compulsão para descobrir quais aspectos desse problema realmente foram corrigidos. Testei vários cenários de impasse em potencial envolvendo tipos de alias, variáveis de tabela e parâmetros com valor de tabela em todas essas compilações; o código é o seguinte:
/* 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 os resultados refletem minha história acima:o SQL Server 2005 RTM não entrou em deadlock em nenhum dos cenários, mas quando o SP4 chegou, todos eles entraram em deadlock. Isso foi corrigido para o cenário "criar um tipo e criar um procedimento", mas nenhum dos outros, em 2008 SP2 e 2008 R2 SP1. Aqui está uma tabela com todos os resultados:
Versão/compilação do SQL Server # | ||||||||||
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 | ||
declarar na tabela var | ||||||||||
criar procedimento | ||||||||||
criar e executar proc | ||||||||||
declarar var local | N/A | |||||||||
criar procedimento | ||||||||||
criar e executar proc |
Conclusão
Portanto, a moral da história é que ainda não há correção para o caso de uso descrito acima, onde você deseja criar um tipo de tabela, criar um procedimento ou função que use o tipo, declarar um tipo, testar o módulo e rolar tudo de volta. De qualquer forma, aqui estão os outros itens do Connect para você ver; Espero que você possa votar neles e deixar comentários descrevendo como esse cenário de impasse afeta diretamente seus negócios:
Espero que alguns esclarecimentos sejam adicionados a esses itens do Connect em um futuro próximo, embora eu não saiba exatamente quando eles serão enviados.