sql >> Databáze >  >> RDS >> Sqlserver

Po zablokování jedné transakce napříč verzemi SQL Server

Jedno z méně běžných uváznutí je takové, kdy existuje jeden uživatel a uvízne na nějakém systémovém prostředku. Nedávno jsem narazil na vytvoření typu aliasu a následné deklarování proměnné tohoto typu uvnitř stejné transakce. Představte si, že se pokoušíte spustit test jednotky nebo test před nasazením, zkontrolujete selhání a v každém případě vrátíte zpět, abyste nezanechali žádnou stopu po tom, co jste udělali. Vzor může vypadat takto:

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

Nebo, pravděpodobněji, trochu složitější:

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;

První místo, kde jsem vyzkoušel tento kód, byl SQL Server 2012 a oba příklady se nezdařily s následující chybou:

Zpráva 1205, úroveň 13, stav 55, řádek 14
Transakce (ID procesu 57) uvízla na prostředcích zámku s jiným procesem a byla vybrána jako oběť zablokování. Spusťte transakci znovu.

A z grafu uváznutí se toho nedá moc naučit:

Když jsem se vrátil o několik let zpět, vzpomínám si, když jsem se poprvé dozvěděl o typech aliasů, zpět na SQL Server 2000 (kdy se jim říkalo User-Defined Data Types). V té době by k tomuto zablokování, na které jsem narazil nedávno, nedošlo (ale je to alespoň částečně proto, že jste nemohli deklarovat proměnnou tabulky s typem aliasu – viz zde a zde). Spustil jsem následující kód na SQL Server 2000 RTM (8.0.194) a SQL Server 2000 SP4 (8.0.2039) a běžel v pořádku:

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;

Samozřejmě, že tento scénář nebyl v té době příliš rozšířený, protože koneckonců jen málo lidí zpočátku používalo aliasové typy. I když mohou vaše metadata více zdokumentovat a podobat se definici dat, jsou královskou bolestí, pokud je někdy budete chtít změnit, což může být téma pro další příspěvek.

Přišel SQL Server 2005 a zavedl novou syntaxi DDL pro vytváření typů aliasů:CREATE TYPE . To ve skutečnosti nevyřešilo problém se změnou typů, jen to trochu zpřehlednilo syntaxi. V RTM všechny výše uvedené ukázky kódu fungovaly dobře bez zablokování. V SP4 by se však všechny zablokovaly. Proto někde mezi RTM a SP4 změnili interní zpracování transakcí, které zahrnovaly proměnné tabulky pomocí typů aliasů.

Rychle vpřed o několik let na SQL Server 2008, kde byly přidány parametry s hodnotou tabulky (viz příklad dobrého použití zde). Díky tomu bylo používání těchto typů mnohem rozšířenější a zavedlo to další případ, kdy by transakce, která se pokusila vytvořit a použít takový typ, uvázla na mrtvém bodě:

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

Zkontroloval jsem Connect a našel jsem několik souvisejících položek, jedna z nich tvrdí, že tento problém byl opraven v SQL Server 2008 SP2 a 2008 R2 SP1:

Connect #365876 :Zablokování nastává při vytváření uživatelem definovaného datového typu a objektů, které jej používají

Ve skutečnosti se to týkalo následujícího scénáře, kde by jednoduché vytvoření uložené procedury odkazující na typ v proměnné tabulky uvázlo v SQL Server 2008 RTM (10.0.1600) a 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;

To však nezablokuje SQL Server 2008 SP3 (10.0.5846) nebo 2008 R2 SP2 (10.50.4295). Takže mám tendenci věřit komentářům k položce Connect – že tato část chyby byla opravena v 2008 SP2 a 2008 R2 SP1 a nikdy nebyla problémem v modernějších verzích.

To však stále vynechává možnost skutečně podrobit typ aliasu jakémukoli skutečnému testování. Takže mé jednotkové testy by uspěly, dokud jsem chtěl pouze otestovat, že dokážu vytvořit proceduru – zapomeňte na deklarování typu jako lokální proměnné nebo jako sloupce v lokální proměnné tabulky.

Jediným způsobem, jak to vyřešit, je vytvořit typ tabulky před zahájením transakce a poté ji explicitně vypustit (nebo ji jinak rozdělit do více transakcí). To by mohlo být extrémně těžkopádné, nebo dokonce nemožné, aby často automatizované testovací rámce a svazky zcela změnily způsob jejich fungování, aby zohlednily toto omezení.

Rozhodl jsem se tedy projít několika testy v počátečních a nejnovějších sestaveních všech hlavních verzí:SQL Server 2005 RTM, 2005 SP4, 2008 RTM, 2008 SP3, 2008 R2 RTM, 2008 R2 SP2, 2012 RTM, 2012 SP1, a 2014 CTP2 (a ano, mám je všechny nainstalované). Zkontroloval jsem několik položek Connect a různé komentáře, které mě nechaly přemýšlet, které případy použití jsou podporovány a kde, a měl jsem zvláštní nutkání zjistit, které aspekty tohoto problému byly skutečně opraveny. Testoval jsem různé potenciální scénáře zablokování zahrnující typy aliasů, proměnné tabulky a parametry s hodnotou tabulky proti všem těmto sestavením; kód je následující:

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

A výsledky odrážejí můj příběh výše:SQL Server 2005 RTM neuvízl na mrtvém bodě v žádném ze scénářů, ale v době, kdy se objevil SP4, uvázly všechny. To bylo opraveno pro scénář "vytvořit typ a vytvořit postup", ale žádný z ostatních, v roce 2008 SP2 a 2008 R2 SP1. Zde je tabulka se všemi výsledky:

Verze serveru SQL / sestavení #
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
Typ aliasu deklarujte v tabulce var
vytvořit postup
vytvořit a spustit proc
Typ tabulky deklarovat místní var Není k dispozici
vytvořit postup
vytvořit a spustit proc

Závěr

Takže morálka příběhu je taková, že stále neexistuje žádná oprava pro případ použití popsaný výše, kde chcete vytvořit typ tabulky, vytvořit proceduru nebo funkci, která používá typ, deklarovat typ, otestovat modul a vrátit všechno zpět. V každém případě jsou zde další položky Connect, na které se můžete podívat; doufám, že pro ně můžete hlasovat a zanechat komentáře popisující, jak tento scénář zablokování přímo ovlivňuje vaši firmu:

  • Připojení #581193 :Vytvoření typu tabulky a její použití ve stejné transakci způsobí uváznutí
  • Připojení #800919 :Problém při vytváření funkce s návratem TableValue Typ transakce s uživatelem definovaným typem v tabulce, která je vytvořena ve stejném rozsahu transakce
  • Connect #804365 :Zablokování nastane, když je vytvořen a použit uživatelsky definovaný typ tabulky v jedné transakci

    Očekávám, že v blízké budoucnosti budou k těmto položkám Connectu přidána některá upřesnění, i když přesně nevím, kdy budou prosazeny.


    1. Schéma zpětného inženýrství (orákula) k ERD

    2. Jak změnit názvy tabulek MySQL na serveru Linux tak, aby nerozlišovaly malá a velká písmena?

    3. Jak přejmenovat databázi SQL Server pomocí T-SQL

    4. Stránkování s Oracle