2

ライブアプリでデッドロックエラーが発生し、(SQL Server Profilerの「デッドロックグラフ」を使用after insertして)テーブルで定義されたトリガーまで追跡しました。

基本的にシナリオはそうです-特定のテーブルに挿入されたレコードを時間枠ごとにグループ化して追跡したいと思います。(つまり、12:00〜12:10の間に、7つのレコードがに挿入されましたUsers)。
私が実装した方法はafter insert、これらのテーブルにトリガーを作成することでした。そのため、レコードが挿入されると、統計テーブルの適切なレコードを更新します。(下記参照)。

私が言ったように、これはデッドロック状態を生み出すようです。何が起こるかというと(私はそれについて確信する方法を見つけていないと思います)、各トランザクションがコミットする前にいくつかのテーブルにいくつかのレコードを挿入/更新する可能性があります。
したがって、トランザクション1が実行され、統計テーブルの特定のレコードが更新され(したがってロックされ)、次にテーブルBのレコードが更新されます。
一方、トランザクション2はレコードをテーブルBに挿入し(したがってロックし)、次のことを試みます。統計テーブルのレコードを更新すると、デッドロックが発生します。

(もちろん、これは起こり得ることの非常に単純化されたバージョンです。実際には、私はまだ100%確実ではありません)。

さて、私の最初の考えは、コミット後にトリガーを実行して、トランザクションがロックを保持しないようにすることが可能かどうかを確認することでした。
しかし、私が理解できる限り、そのような選択肢はありません。

別の解決策は、トリガーを完全に排除し、代わりにある種のバッチジョブを使用することです。

望ましい解決策についての他のアイデア/考えは大歓迎です。

トリガーコード:

SELECT  @TimeIn = I.TimeIn,
    @TimeOut = I.[TimeOut],
    FROM    Inserted AS I

        SET     @NoOfPAX = 1

        SET     @Day = DATEADD(dd,0,DATEDIFF(dd,0,@TimeOut))
        SET     @HourOfTheDay = DATEPART (HOUR, @TimeOut) 
        SET     @MinuteOfTheHour = DATEPART (MINUTE, @TimeOut)  

        SELECT  @HourlyStatsExists = COUNT(*)
        FROM    dbo.DataWarehouse_HourlyStats
        WHERE   [Day] = @Day
            AND HourOfTheDay = @HourOfTheDay
            AND MinuteOfTheHour = @MinuteOfTheHour

        IF      @HourlyStatsExists = 0
        BEGIN

                INSERT INTO dbo.DataWarehouse_HourlyStats
                (
                 HourOfTheDay, 
                 MinuteOfTheHour, 
                 [Day], 
                 Total
                )
                VALUES (
                 @HourOfTheDay, 
                 @MinuteOfTheHour, 
                 @Day, 
                 @NoOfPAX 
                )

        END
        ELSE
        BEGIN    


                UPDATE  DataWarehouse_HourlyStats
                SET     Total = Total + @NoOfPAX,
                        LastUpdate = GetDate()

                WHERE   [Day] = @Day
                    AND HourOfTheDay = @HourOfTheDay
                    AND MinuteOfTheHour = @MinuteOfTheHour

        END
4

1 に答える 1

5

あなたがしていた最悪のことは、存在をテストするためだけに非常に高価なカウントを取得することでした。これはコストがかかり、不要です。特に、更新するか挿入するかを決定するためにこれを使用していたためです。複数行の挿入の場合、両方を実行する必要がある場合があります。

手順1.存在するものを更新します

;WITH x AS 
(
    SELECT d = DATEADD(DAY, 0, DATEDIFF(DAY, 0, i.TimeOut)),
           h = DATEPART(HOUR,   i.TimeOut),
           m = DATEPART(MINUTE, i.Timeout)
    FROM inserted
),
y AS 
(
    SELECT d, h, m, Total = COUNT(*)
    FROM x GROUP BY d, h, m
)
UPDATE h
    SET Total += y.Total,
    LastUpdate = CURRENT_TIMESTAMP        
FROM dbo.DataWarehouse_HourlyStats AS h
INNER JOIN y
    ON h.[Day] = y.d
    AND h.HourOfTheDay = y.h
    AND h.MinuteOfTheHour = y.m;

ステップ2.挿入しないものを挿入します

;WITH x AS 
(
    SELECT d = DATEADD(DAY, 0, DATEDIFF(DAY, 0, i.TimeOut)),
           h = DATEPART(HOUR,   i.TimeOut),
           m = DATEPART(MINUTE, i.Timeout)
    FROM inserted
),
y AS 
(
    SELECT d, h, m, Total = COUNT(*)
    FROM x WHERE NOT EXISTS
    (
      SELECT 1 FROM dbo.DataWarehouse_HourlyStats
      WHERE [Day] = x.d
      AND HourOfTheDay = x.h
      AND MinuteOfTheHour = x.m
    ) 
    GROUP BY d, h, m
)
INSERT dbo.DataWarehouse_HourlyStats
(
    HourOfTheDay, 
    MinuteOfTheHour, 
    [Day],
    Total
)
SELECT h,m,d,Total
    FROM y;

より多くのコードのように見えますが、これは既存のバージョンよりも効率的で正確です。

とはいえ、インサートがたくさんあるかどうかに関係なく、かなり高価なトリガーになります。少なくとも、Day、Hour、Minuteの列に適切なサポートインデックスがあることを願っています。

たぶん、1時間ごとまたは1日ごとのバッチジョブを使用して統計をコンパイルする方がよいでしょう。トリガー自体は定義上トランザクションの一部であるため、データをキューまたはバックグラウンドテーブルに詰め込み、他のジョブで修正しない限り、コミットを遅らせることはできません。

于 2011-09-05T15:40:06.083 に答える