10

トリガーを追加した後、デッドロックが発生しています。UserBalanceHistoryトランザクションごとに 1 つの行と 1 つの列を持つテーブルがありますAmount。列を合計しAmount、結果を関連Userテーブル列に配置するトリガーが追加されましたBalance

CREATE TABLE [User]
(
    ID INT IDENTITY,
    Balance MONEY,
    CONSTRAINT PK_User PRIMARY KEY (ID)
);

CREATE TABLE UserBalanceHistory
(
    ID INT IDENTITY,
    UserID INT NOT NULL,
    Amount MONEY NOT NULL,
    CONSTRAINT PK_UserBalanceHistory PRIMARY KEY (ID),
    CONSTRAINT FK_UserBalanceHistory_User FOREIGN KEY (UserID) REFERENCES [User] (ID)
);

CREATE NONCLUSTERED INDEX IX_UserBalanceHistory_1 ON UserBalanceHistory (UserID) INCLUDE (Amount);

CREATE TRIGGER TR_UserBalanceHistory_1 ON UserBalanceHistory AFTER INSERT, UPDATE, DELETE AS
BEGIN
    DECLARE @UserID INT;

    SELECT TOP 1 @UserID = u.UserID
    FROM
    (
            SELECT UserID FROM inserted
        UNION
            SELECT UserID FROM deleted
    ) u;

    EXEC dbo.UpdateUserBalance @UserID;
END;

CREATE PROCEDURE UpdateUserBalance
    @UserID INT
AS
BEGIN
    DECLARE @Balance MONEY;

    SET @Balance = (SELECT SUM(Amount) FROM UserBalanceHistory WHERE UserID = @UserID);

    UPDATE [User]
    SET Balance = ISNULL(@Balance, 0)
    WHERE ID = @UserID;
END;

私もオンにしましたREAD_COMMITTED_SNAPSHOT

ALTER DATABASE MyDatabase SET READ_COMMITTED_SNAPSHOT ON;

エントリを作成している並列プロセスを実行していUserBalanceHistoryますが、明らかに同時に同じ作業をしている場合User、デッドロックが発生します。提案?

4

3 に答える 3

3

デッドロックは、UserBalanceHistory-> UserBalanceHistory-> Userにアクセスしているのに対し、他の更新はUser->UserBalanceHistoryであるために発生します。ロックの粒度やインデックスロックなどのため、それよりも複雑です。

根本的な原因は、おそらくUserBalanceHistoryでのUserIDとAmountのスキャンです。(UserID) INCLUDE (Amount)これを変更するには、UserBalanceHistoryにインデックスを付けます

SNAPSHOT分離モデルは依然としてデッドロックする可能性があります:そこに例があります(1、2

最後に、異なる複数の更新パスを回避するために、すべてを1つにまとめてみませんか?

CREATE TRIGGER TR_UserBalanceHistory_1 ON UserBalanceHistory AFTER INSERT, UPDATE, DELETE AS
BEGIN
    DECLARE @UserID INT;

    UPDATE U
    SET Balance = ISNULL(t2.Balance, 0)
    FROM
       (
         SELECT UserID FROM INSERTED
         UNION
         SELECT UserID FROM DELETED
       ) t1
       JOIN
       [User] U ON t1.UserID = u.UserID
       LEFT JOIN
       (
        SELECT UserID, SUM(Amount) AS Balance
        FROM UserBalanceHistory
        GROUP BY UserID
       ) t2 ON t1.UserID = t2.UserID;

END;
于 2011-06-08T17:13:16.680 に答える
0

UserBalanceHistory テーブルでクラスター化されたキーをユーザー ID に変更し、非クラスター化インデックスを削除します。ユーザー ID を使用してテーブルにアクセスしているため、クラスター化インデックスに ID 列を使用する理由はありません。を使用してから、クラスター化インデックスから読み取って金額の値を変更します。クラスター化インデックスは、バランスを合計するときに行う範囲検索に最適です。現在の状況では、ユーザーの支払いを取得するためだけに SQL がテーブル内のすべてのデータ ページを要求する可能性があります。クラスター化されたインデックスの一部の断片化は、単一のユーザー ID の連続 (sp) リンクされたページによって相殺されます。クラスタを変更して非クラスタを削除すると、時間とメモリが節約されます。
SP の終了中にトリガーされたテーブルがロックされるため、トリガーからストアド プロシージャを実行しないでください。

残高テーブルは、UserBalanceHistory テーブルの計算列 (SO リンクはこちら) を持つビューから作成できます。

開発システムでテストしてから、もう一度テストしてください!

于 2011-06-08T20:58:55.543 に答える