触发器导致死锁?
添加触发器后,我陷入了僵局。有一个 UserBalanceHistory
表,其中每笔交易占一行,还有一个 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 条目,显然它是否在同一个用户上工作同时,出现死锁。建议?
I'm running into a deadlock after I added a trigger. There is a UserBalanceHistory
table which has one row for each transaction and an Amount
column. A trigger was added to sum the Amount
column and place the result in the related User
table, Balance
column.
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;
I've also turned on READ_COMMITTED_SNAPSHOT
:
ALTER DATABASE MyDatabase SET READ_COMMITTED_SNAPSHOT ON;
I have a parallel process running which is creating UserBalanceHistory
entries, apparently if it is working on the same User
at the same time, the deadlock occurs. Suggestions?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这是一个老问题了,但我想如果其他人遇到它,我就找到了答案。当然是我的答案。
问题可能是 UserBalanceHistory 和 User 之间存在 FK 约束。在这种情况下,对 UserBalanceHistory 的两个并发插入可能会发生死锁。
这是因为在插入 UserBalanceHistory 时,数据库将在 User 上获取共享锁以查找 FK 的 ID。然后,当触发器触发时,它将对用户进行独占锁定。
如果这种情况同时发生,则这是典型的锁升级死锁,其中两个事务都无法升级为独占锁,因为另一个事务持有共享锁。
我的解决方案是在更新和插入时无偿地加入到 User 表,并在该表上使用WITH (UPDLOCK) 提示。
Way old question, but I think I just found the answer if anyone else comes across it. Certainly was the answer for me.
The issue is probably that there is a FK constraint between UserBalanceHistory and User. In this case two concurrent inserts to UserBalanceHistory can deadlock.
This is because on the insert to UserBalanceHistory the database will take a shared lock on User to lookup the ID for the FK. Then when the trigger fires, it will take an exclusive lock on User.
If this happens concurrently, it's a classic lock escalation deadlock, where neither transaction can escalate to an exclusive lock because the other is holding a shared lock.
My solution was to gratuitously join to the User table on updates and inserts and use a WITH (UPDLOCK) hint on that table.
发生死锁是因为您正在访问 UserBalanceHistory ->用户余额历史记录 -> User 而其他一些更新是 User ->用户余额历史记录。由于锁粒度和索引锁等原因,它比这更复杂。
根本原因可能是在 UserBalanceHistory 上扫描 UserID 和 Amount。我在 UserBalanceHistory 上的
(UserID) INCLUDE (Amount)
上有一个索引来更改此SNAPSHOT 隔离模型仍然可能陷入死锁:那里有一些示例 (一个,两个
最后,为什么不将这一切都集中在一起以避免不同的和多个更新路径呢?
The deadlock happens because you are accessing UserBalanceHistory -> UserBalanceHistory -> User whereas some other update is User -> UserBalanceHistory. It's more complex than that because of lock granularity and index locks etc.
The root cause is probably a scan on UserBalanceHistory for UserID and Amount. I'd have an index on
(UserID) INCLUDE (Amount)
on UserBalanceHistory to change thisSNAPSHOT isolation models can still deadlock: there are examples out there (One, Two
Finally, Why not do it all in one to avoid different and multiple update paths?
将 UserBalanceHistory 表中的聚集键更改为 userid 并删除非聚集索引,因为您使用 userid 访问表,没有理由为聚集索引使用标识列,因为它总是会强制非聚集索引使用,然后从聚集索引中读取以更改货币值。聚集索引最适合范围搜索,这就是您在求和余额时所做的事情。您目前的情况可能会导致 SQL 请求表中的每个数据页只是为了获取用户付款,聚集索引中的一些碎片被单个用户 ID 的连续(sp)链接页所抵消。更改集群并删除非集群将节省时间和内存。
不要从触发器运行任何存储过程,因为它将在 SP 完成时锁定触发的表。
余额表可以从带有计算列的视图中创建(SO链接此处)在 UserBalanceHistory 表上。
在开发系统中进行测试,然后再次测试!
Change the clustered key to userid in your UserBalanceHistory table and drop the non-clustered index because you are using userid to access the table there is no reason to use an identity column for the clustered index as it will always force the non-clustered index to be used and then a read from the clustered index to the change the money value. Clustered indexes are best for range searches which is what you are doing when you sum the balance. Your present situation may cause SQL to request every data page in the table just to get the user payments, some fragmentation in the clustered index is offset by the contigiously(sp) linked pages for a single userid. Changing the cluster and dropping the non-cluster will save time and memory.
Do not run any stored proc from the trigger because it will lock the triggered table while the SP finishes.
The balance table could be made from a view with a computed column (SO link here) on the UserBalanceHistory table .
Test in a development system, and then test again!