2

我一直在我的实时应用程序中遇到死锁错误,并已将它们(使用 sql server profiler 的“死锁图”)跟踪到after insert我的表上定义的触发器。

基本上情况是这样的——我想跟踪插入到某些表中的记录,按时间范围分组。(即在 12:00-12:10 之间,有 7 条记录被插入Users)。
我实现它的方式是after insert在这些表上创建触发器,因此当插入记录时,我会更新统计表中的相应记录。(见下文)。

正如我所说,这似乎造成了僵局。发生的事情是(我认为,我还没有找到确定的方法)是每个事务在提交之前可能会在多个表中插入/更新多个记录。
所以事务 1 出现,更新统计表中的某条记录(从而锁定它),然后继续更新表 B 中的记录。
同时,事务 2 将一条记录插入到表 B 中(从而锁定它),并尝试更新统计表中的记录 - 导致死锁。

(这当然是可能发生的事情的一个非常简化的版本。实际上我还不是 100% 确定)。

现在我最初的想法是看看是否有可能在 commit 之后执行触发器,以便事务不再持有任何锁。
但是,据我所知,没有这样的选择。

另一种解决方案是完全消除触发器,并改用某种批处理作业。

欢迎任何其他关于首选解决方案的想法/想法。

触发代码:

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 列上有一个很好的支持索引。

使用每小时或每天的批处理作业来编译统计数据可能会更好。根据定义,触发器本身是事务的一部分,因此您不能延迟提交,除非您只是将数据填充到队列或后台表中并让其他工作修复它。

于 2011-09-05T15:40:06.083 回答