sql >> データベース >  >> RDS >> Sqlserver

SQLServerのバージョン間での単一トランザクションのデッドロックに続く

    あまり一般的ではないデッドロックの1つは、1人のユーザーがいて、システムリソースでデッドロックしている場合です。私が最近遭遇したのは、エイリアスタイプを作成し、同じトランザクション内でそのタイプの変数を宣言することです。単体テストまたは展開前テストを実行し、障害をチェックし、どのような場合でもロールバックして、実行したことの痕跡を残さないようにしようとしていると想像してください。パターンは次のようになります:

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

    または、おそらくもう少し複雑です:

    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;

    このコードを最初に試したのはSQLServer2012でしたが、どちらの例も次のエラーで失敗しました。

    メッセージ1205、レベル13、状態55、行14
    トランザクション(プロセスID 57)は、別のプロセスとのロックリソースでデッドロックされ、デッドロックの犠牲者として選択されました。トランザクションを再実行します。

    そして、デッドロックグラフから学ぶことはほとんどありません:

    数年前にさかのぼると、SQL Server 2000(ユーザー定義データ型と呼ばれていたとき)でエイリアスの種類について最初に学んだときのことを思い出します。そのとき、私が最近遭遇したこのデッドロックは発生しませんでした(ただし、これは少なくとも部分的には、エイリアスタイプでテーブル変数を宣言できなかったためです。こことここを参照してください)。 SQL Server 2000 RTM(8.0.194)およびSQL Server 2000 SP4(8.0.2039)で次のコードを実行しましたが、問題なく実行されました。

    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;

    もちろん、このシナリオは当時あまり普及していませんでした。結局のところ、そもそもエイリアスタイプを使用する人はあまりいなかったからです。メタデータをより自己文書化してデータ定義のようにすることができますが、メタデータを変更したい場合は非常に苦痛です。これは別の投稿のトピックになる可能性があります。

    SQL Server 2005が登場し、エイリアスタイプを作成するための新しいDDL構文が導入されました:CREATE TYPE 。これは、型を変更する際の問題を実際には解決しませんでした。構文が少しわかりやすくなっただけです。 RTMでは、上記のコードサンプルはすべて、デッドロックなしで問題なく機能しました。ただし、SP4では、それらはすべてデッドロックします。したがって、RTMとSP4の間のどこかで、エイリアスタイプを使用するテーブル変数を含むトランザクションの内部処理を変更しました。

    数年早送りしてSQLServer2008に進み、テーブル値のパラメーターが追加されました(ここで適切な使用例を参照してください)。これにより、これらのタイプの使用がはるかに普及し、そのようなタイプを作成して使用しようとしたトランザクションがデッドロックする別のケースが発生しました:

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

    Connectを確認したところ、いくつかの関連項目が見つかりました。そのうちの1つは、この問題がSQL Server2008SP2および2008R2SP1で修正されたと主張しています。

    接続#365876:ユーザー定義のデータ型とそれを使用するオブジェクトを作成するときにデッドロックが発生します

    これが実際に参照したのは、次のシナリオでした。テーブル変数の型を参照するストアドプロシージャを作成するだけで、SQL Server 2008 RTM(10.0.1600)および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;

    ただし、これはSQL Server 2008 SP3(10.0.5846)または2008 R2 SP2(10.50.4295)ではデッドロックしません。そのため、Connectアイテムに関するコメントを信じる傾向があります。バグのこの部分は、2008SP2および2008R2 SP1で修正されており、最新バージョンでは問題になりませんでした。

    しかし、これでも、実際にエイリアスタイプをあらゆる種類の真のテストにかける機能はありません。したがって、プロシージャを作成できるかどうかをテストするだけであれば、単体テストは成功します。型をローカル変数またはローカルテーブル変数の列として宣言することを忘れてください。

    これを解決する唯一の方法は、トランザクションを開始する前にテーブルタイプを作成し、後で明示的にドロップする(または複数のトランザクションに分割する)ことです。多くの場合、自動化されたテストフレームワークとハーネスを使用して、この制限を説明するために動作方法を完全に変更することは、非常に面倒であり、不可能でさえあります。

    そこで、すべてのメジャーバージョンの初期ビルドと最新ビルドでいくつかのテストを行うことにしました:SQL Server 2005 RTM、2005 SP4、2008 RTM、2008 SP3、2008 R2 RTM、2008 R2 SP2、2012 RTM、2012 SP1および2014CTP2(はい、すべてインストールしています)。いくつかのConnectアイテムとさまざまなコメントを確認したところ、どのユースケースがどこでサポートされているのか疑問に思いました。そして、この問題のどの側面が実際に修正されたかを知るために奇妙な衝動に駆られました。これらすべてのビルドに対して、エイリアスタイプ、テーブル変数、およびテーブル値パラメーターを含むさまざまな潜在的なデッドロックシナリオをテストしました。コードは次のとおりです。

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

    そして、結果は上記の私の話を反映しています。SQLServer 2005 RTMはどのシナリオでもデッドロックしませんでしたが、SP4がロールアラウンドするまでに、すべてがデッドロックしました。これは、2008SP2および2008R2 SP1で、「タイプの作成とプロシージャの作成」シナリオで修正されましたが、他のシナリオでは修正されませんでした。すべての結果を示す表を次に示します。

    SQLServerバージョン/ビルド番号
    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
    エイリアスタイプ テーブル変数で宣言
    プロシージャの作成
    procの作成と実行
    テーブルタイプ ローカル変数を宣言する N / A
    プロシージャの作成
    procの作成と実行

    結論

    したがって、話の教訓は、テーブルタイプを作成し、そのタイプを使用するプロシージャまたは関数を作成し、タイプを宣言し、モジュールをテストし、ロールする上記のユースケースの修正はまだないということです。すべてが戻ってきました。いずれにせよ、ここにあなたが見るべき他のConnectアイテムがあります。うまくいけば、あなたはそれらに投票し、このデッドロックシナリオがあなたのビジネスに直接どのように影響するかを説明するコメントを残すことができます:

  1. 接続#581193:テーブルタイプを作成し、それを同じトランザクションで使用すると、デッドロックが発生します
  2. Connect#800919:同じトランザクションスコープで作成されたテーブルのユーザー定義型のトランザクションでTableValue戻り型を使用して関数を作成する際の問題
  3. 接続#804365:ユーザー定義のテーブルタイプが作成され、1つのトランザクションで使用されると、デッドロックが発生します

    近い将来、これらのConnectアイテムにいくつかの説明が追加されることを完全に期待していますが、いつプッシュスルーされるかは正確にはわかりません。


    1. T-SQLのピボットデータ

    2. PostgreSQLで自動インクリメントの主キーを設定するにはどうすればよいですか?

    3. 制約の名前がわからない場合、Oracleで非ヌル制約を削除するにはどうすればよいですか?

    4. MariaDBコンポジットの日付と時刻の単位の説明