Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Após um impasse de transação única nas versões do SQL Server


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
Tipo de alias declarar na tabela var
criar procedimento
criar e executar proc
Tipo de tabela 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:
  • Connect #581193 :Criar um tipo de tabela e usá-lo na mesma transação causa um impasse
  • Connect #800919 :Problema ao criar uma função com TableValue Return Type em transação com tipo definido pelo usuário na tabela que é criada em um mesmo escopo de transação
  • Connect #804365 :O deadlock ocorre quando um tipo de tabela definido pelo usuário é criado e usado em uma transação
    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.