web-dev-qa-db-ja.com

同じトリガーテーブルの列を更新する挿入トリガーを持つテーブルの挿入時のデッドロック

仕様により、各テーブルは主キーとしてGUIDを使用します。別の要件は、各リソースが6つの番号識別子(100001、100002、...)を、リソースを所有する顧客(Resource -> Location -> Customer)。

挿入後トリガーを使用して、この6つの数値を設定しようとしました。ロジックは正常に機能しますが、着信する多くの挿入をシミュレートするとデッドロックが発生します。tablockxヒントを挿入に追加することで、このデッドロックを解消できます。ただし、Linq2SQLを使用してデータを挿入するので、挿入にロックヒントを追加する必要はありません。代わりに、トリガーのロックヒントまたはインデックスヒントのいくつかの組み合わせが機能することを望んでいました。

スキーマ:

CREATE TABLE [Resource] (
    [pkGUID]                UNIQUEIDENTIFIER NOT NULL,
    [nIdentifier]           INT NOT NULL DEFAULT 0,
    [fkLocation]            UNIQUEIDENTIFIER NOT NULL,
    CONSTRAINT [PK_Resource] PRIMARY KEY CLUSTERED ([pkGUID] ASC)
)
CREATE TABLE [Location] (
    [pkGUID]                UNIQUEIDENTIFIER NOT NULL,
    [fkCustomer]            UNIQUEIDENTIFIER NOT NULL,
    CONSTRAINT [PK_Location] PRIMARY KEY CLUSTERED ([pkGUID] 
)
CREATE TABLE [Customer] (
    [pkGUID]                UNIQUEIDENTIFIER NOT NULL,
    CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED ([pkGUID] 
)

現在、クラスター化された主キーを除いて、Resourceテーブルにはインデックスがありません。

引き金:

CREATE TRIGGER [dbo].[Trigger_Resource_Insert]
ON [dbo].[Resource]
FOR INSERT
AS
BEGIN
    SET NoCount ON

    ;with relevantCustomers as (
        select c.pkGUID
        from inserted i
        join Location l on i.fkLocation = l.pkGUID
        join Customer c on l.fkCustomer = c.pkGUID
        group by c.pkGUID
    ), maxNumbers as (
        select rc.pkGUID, max = case when max(x.nIdentifier) < 100000 then 100000 else max(x.nIdentifier) end
        from relevantCustomers rc
        join Location l on l.fkCustomer = rc.pkGUID
        join Resource x on x.fkLocation = l.pkGUID
        group by rc.pkGUID
    ), numbers as (
        select i.pkGUID, number = row_number() over (partition by n.pkGUID order by i.pkGUID) + n.max
        from inserted i
        join Location l on i.fkLocation = l.pkGUID
        join maxNumbers n on n.pkGUID = l.fkCustomer
    )
    update x
    set nIdentifier = n.number
    from Resource x
    join numbers n on x.pkGUID = n.pkGUID
END

一連のデータを生成する挿入:

;with x as (
    select num = 1
    union all
    select x.num + 1
    from x
    where x.num < {0}
), l as (
    select top {1} fkLocation = pkGUID, sLocationName from Location order by sLocationName
)
insert into Resource with (tablockx) (pkGUID, fkLocation, ...)
select newid(), l.fkLocation, ...
from x
cross join l

tablockxヒントなしで挿入を実行すると、SQLプロファイラからのデッドロックグラフ:

Deadlock graph

4
sh54

私は、あなたが見ているものは必ずしもフレームワークの問題であると述べているコメントの一部にはまったく同意しないと言わざるを得ません。 Linq to SQLフレームワーク ほとんどの場合そうです では、ストアドプロシージャを行の入力手段として指定できます。

そうは言っても、可能であれば、トリガーの代わりにそのメカニズムを使用することを強くお勧めします。ストアドプロシージャ内にロックヒントを追加でき、挿入は正常に機能します。

しかし、あなたの仕様は私には非常に疑わしいようです。IMOで現在発生していると思われる問題は、このような設計を追求したくない理由の良い反例です。だから私の純粋な修辞的な質問はデザイナーのためのものです。問題のある連番は何を表しているのですか?それが時間のシーケンスである場合は、時間を使用し、ウィンドウ関数を使用してビューのようなもので行を並べ替えないのはなぜですか(LinqからSQLをビューにポイントすることもできます)?なんらかのギャップのない連番を付けるつもりですか?その数を維持するためだけに対処する必要がある複雑さとロックのすべてにサインオンしていますか?

残念ながら、本当の答えはありません-フレームワークとデザイン全体で機能するヒントの魔法の組み合わせはありません-あなたの問題を解決します。 IMO私の答えは一歩下がって、あなたのデザインを批判的に見ることです。

2
Dave Markle

トリガーにはコードが多すぎるようです。

一歩下がって、達成しようとしていることを見てみましょう。

挿入したレコードにnIdentifierを設定したい。

私の解決策は、トリガーをまったく使用しないことです。

  1. レコードを挿入し、OUTPUTを使用して一時テーブルに挿入したものをキャプチャします
  2. tempテーブルを使用してResourceテーブルに結合し、更新を行います
1
Jimbo