触发器的死锁问题
Posted
技术标签:
【中文标题】触发器的死锁问题【英文标题】:deadlock issues with triggers 【发布时间】:2011-09-05 13:44:26 【问题描述】:我在我的实时应用程序中遇到了死锁错误,并已将它们(使用 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
【问题讨论】:
您的触发器尚未准备好进行多行插入。如果存在多行插入,您希望获得哪个 TimeIn 和 TimeOut 值? 如果您使用的是企业版,请查看更改数据捕获,它使用日志阅读器从插入中异步获取更改。 【参考方案1】:你做的最糟糕的事情是为了测试存在而进行极其昂贵的计数。这是昂贵且不必要的 - 特别是因为您使用它来决定是否要更新 OR 插入。在多行插入的情况下,您可能需要同时执行这两项操作。
第 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 列上有一个很好的支持索引。
最好使用每小时或每天的批处理作业来编译统计信息。根据定义,触发器本身是事务的一部分,因此您不能延迟提交,除非您只是将数据填充到队列或后台表中并让其他工作修复它。
【讨论】:
虽然这是一个可靠的建议,但它根本没有解决我的问题(僵局)。 其实是这样,索引后,开始攻击死锁的最好方法是缩短事务时间。 当然,我消除了事务的一个昂贵部分,而且我实际上已经看到这种确切类型的更改消除了死锁。它不在触发器中,但他们首先检查计数以决定是更新还是插入。摆脱了计数,摆脱了僵局。 实际上我们做到了 + 在相关表中添加了一些缺失的索引(愚蠢的 DBA...),这似乎起到了作用。以上是关于触发器的死锁问题的主要内容,如果未能解决你的问题,请参考以下文章