sql >> Base de Datos >  >> RDS >> Sqlserver

Después de un punto muerto de transacción única en las versiones de SQL Server

Uno de los puntos muertos menos comunes es aquel en el que hay un solo usuario y se bloquean ellos mismos en algún recurso del sistema. Uno reciente que encontré es crear un tipo de alias y luego declarar una variable de ese tipo, dentro de la misma transacción. Imagine que está tratando de ejecutar una prueba unitaria o una prueba previa a la implementación, verificar fallas y revertir en cualquier caso para no dejar ningún rastro de lo que ha hecho. El patrón podría verse así:

BEGIN TRANSACTION;
GO
CREATE TYPE EmailAddress FROM VARCHAR(320);
GO
DECLARE @x TABLE (e EmailAddress);
GO
ROLLBACK TRANSACTION;

O, más probablemente, un poco más complejo:

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;

El primer lugar donde probé este código fue SQL Server 2012, y ambos ejemplos fallaron con el siguiente error:

Mensaje 1205, nivel 13, estado 55, línea 14
La transacción (ID de proceso 57) se interbloqueó en los recursos bloqueados con otro proceso y se eligió como víctima del interbloqueo. Vuelva a ejecutar la transacción.

Y no hay mucho que aprender del gráfico de punto muerto:

Retrocediendo unos años, recuerdo la primera vez que aprendí sobre los tipos de alias, en SQL Server 2000 (cuando se llamaban Tipos de datos definidos por el usuario). En ese momento, este interbloqueo que encontré más recientemente no ocurriría (pero esto se debe, al menos en parte, a que no se podía declarar una variable de tabla con un tipo de alias; consulte aquí y aquí). Ejecuté el siguiente código en SQL Server 2000 RTM (8.0.194) y SQL Server 2000 SP4 (8.0.2039), y funcionó perfectamente:

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;

Por supuesto, este escenario no estaba muy extendido en ese momento porque, después de todo, no mucha gente usaba tipos de alias en primer lugar. Si bien pueden hacer que sus metadatos sean más autodocumentados y similares a la definición de datos, son un dolor real si alguna vez desea cambiarlos, lo que puede ser un tema para otra publicación.

SQL Server 2005 apareció e introdujo una nueva sintaxis DDL para crear tipos de alias:CREATE TYPE . Esto realmente no resolvió el problema de cambiar los tipos, solo hizo que la sintaxis fuera un poco más limpia. En RTM, todos los ejemplos de código anteriores funcionaron bien sin interbloqueos. En SP4, sin embargo, todos estarían en punto muerto. Por lo tanto, en algún lugar entre RTM y SP4, cambiaron el manejo interno de las transacciones que involucraban variables de tabla usando tipos de alias.

Avancemos unos años hasta SQL Server 2008, donde se agregaron parámetros con valores de tabla (vea un buen caso de uso aquí). Esto hizo que el uso de estos tipos fuera mucho más frecuente e introdujo otro caso en el que una transacción que intentaba crear y usar dicho tipo se bloquearía:

BEGIN TRANSACTION;
GO
CREATE TYPE dbo.Items AS TABLE(Item INT);
GO
DECLARE @r dbo.Items;
GO
ROLLBACK TRANSACTION;

Revisé Connect y encontré varios elementos relacionados, uno de ellos afirma que este problema se solucionó en SQL Server 2008 SP2 y 2008 R2 SP1:

Conexión n.º 365876:se produce un interbloqueo al crear un tipo de datos definido por el usuario y los objetos que lo utilizan

A lo que esto realmente se refería era al siguiente escenario, donde simplemente crear un procedimiento almacenado que hiciera referencia al tipo en una variable de tabla provocaría un punto muerto en SQL Server 2008 RTM (10.0.1600) y 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;

Sin embargo, esto no bloquea en SQL Server 2008 SP3 (10.0.5846) o 2008 R2 SP2 (10.50.4295). Así que tiendo a creer los comentarios sobre el elemento Connect:que esta parte del error se solucionó en 2008 SP2 y 2008 R2 SP1, y nunca ha sido un problema en versiones más modernas.

Pero esto aún deja fuera la capacidad de someter el tipo de alias a cualquier tipo de prueba real. Por lo tanto, mis pruebas unitarias tendrían éxito siempre que todo lo que quisiera hacer fuera probar que podía crear el procedimiento; olvídese de declarar el tipo como una variable local o como una columna en una variable de tabla local.

La única forma de resolver esto es crear el tipo de tabla antes de iniciar la transacción y descartarlo explícitamente después (o dividirlo en varias transacciones). Esto podría ser extremadamente engorroso, o incluso imposible, ya que los marcos de prueba y los arneses a menudo automatizados cambian por completo la forma en que funcionan para dar cuenta de esta limitación.

Así que decidí realizar algunas pruebas en las compilaciones iniciales y más recientes de todas las versiones principales:SQL Server 2005 RTM, 2005 SP4, 2008 RTM, 2008 SP3, 2008 R2 RTM, 2008 R2 SP2, 2012 RTM, 2012 SP1, y 2014 CTP2 (y sí, los tengo todos instalados). Había revisado varios elementos de Connect y varios comentarios que me hicieron preguntarme qué casos de uso eran compatibles y dónde, y tenía una extraña compulsión por averiguar qué aspectos de este problema se habían solucionado. Probé varios escenarios potenciales de punto muerto que involucraban tipos de alias, variables de tabla y parámetros con valores de tabla en todas estas compilaciones; el código es el siguiente:

/* 
  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;

Y los resultados reflejan mi historia anterior:SQL Server 2005 RTM no se bloqueó en ninguno de los escenarios, pero para cuando llegó el SP4, todos se bloquearon. Esto se corrigió para el escenario "crear un tipo y crear un procedimiento", pero ninguno de los otros, en 2008 SP2 y 2008 R2 SP1. Aquí hay una tabla que muestra todos los resultados:

Versión de SQL Server/n.° de compilación
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 en tabla var
crear procedimiento
crear y ejecutar proceso
Tipo de tabla declarar var local N/A
crear procedimiento
crear y ejecutar proceso

Conclusión

Entonces, la moraleja de la historia es que todavía no hay solución para el caso de uso descrito anteriormente, donde desea crear un tipo de tabla, crear un procedimiento o función que use el tipo, declarar un tipo, probar el módulo y ejecutar todo de vuelta. En cualquier caso, aquí están los otros elementos de Connect para que los mires; con suerte, puede votar por ellos y dejar comentarios que describan cómo este escenario de punto muerto afecta directamente a su negocio:

  • Conectar #581193:crear un tipo de tabla y usarlo en la misma transacción provoca un interbloqueo
  • Conexión n.° 800919:Problema al crear una función con el tipo de devolución TableValue en una transacción con un tipo definido por el usuario en la tabla que se crea en el mismo ámbito de transacción
  • Connect #804365 :se produce un interbloqueo cuando se crea un tipo de tabla definido por el usuario y se usa en una transacción

    Espero que se agreguen algunas aclaraciones a estos elementos de Connect en un futuro próximo, aunque no sé exactamente cuándo se aprobarán.