我正在开发一款在线游戏的在线时刻系统。问题是有时SQL会抛出死锁错误:
40001 Transaction (Process ID 411) was deadlocked on lock | communication buffer resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
游戏有5个房间,每个房间都是一个有自己SQL连接的应用程序。玩家无法登录多个房间。每个应用程序每10分钟在在线播放器上循环,将命令发送到SQL过程以更新其在线时间......
EXEC StoredProcedure 'PlayerName', 7
按照存储过程代码:
USE [DataBase]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[StoredProcedure]
@PlayerName varchar(10),
@Minutes int
AS
BEGIN
DECLARE @PlayerLogin varchar(10),
@OnlineMinutes int
SELECT @PlayerLogin = PlayerLogin FROM [dbo].[PlayerTable] WHERE PlayerName = @PlayerName
UPDATE [dbo].[PlayerTable] SET OnlineMinutes = OnlineMinutes + @Minutes WHERE PlayerName = @PlayerName
UPDATE [dbo].[AccountTable] SET OnlineMinutes = OnlineMinutes + @Minutes WHERE PlayerLogin = @PlayerLogin
SELECT @OnlineMinutes = OnlineMinutes FROM [dbo].[AccountTable] WHERE PlayerLogin = @PlayerLogin
IF ( @OnlineMinutes >= 60 )
BEGIN
UPDATE [dbo].[AccountTable] SET OnlineHours = OnlineHours + ( @OnlineMinutes / 60 ), OnlineMinutes = ( @OnlineMinutes % 60 ) WHERE PlayerLogin = @PlayerLogin
END
END
在两个表格中存储在线会议记录的原因是因为PlayerTable
用于排名页面,而AccountTable
在线时间用作玩家花费的虚拟现金在网上小时游戏商店。
答案 0 :(得分:0)
我建议使用WITH(ROWLOCK)
选项:
UPDATE [dbo].[PlayerTable] WITH (ROWLOCK)
SET OnlineMinutes = OnlineMinutes + @Minutes
WHERE PlayerName = @PlayerName
答案 1 :(得分:0)
我建议进行以下更改。
1)在您的调用应用程序中,您跟踪@PLayerName并将其传递给此过程。然后使用它来查找@PlayerLogin。您还应该在调用者中跟踪@PlayerName并将两个变量传递给此过程,从而避免在proc中查找@PlayerLogin
2)替换
SELECT @OnlineMinutes = OnlineMinutes FROM [dbo].[AccountTable] WHERE PlayerLogin = @PlayerLogin
IF ( @OnlineMinutes >= 60 )
BEGIN
UPDATE [dbo].[AccountTable] SET OnlineHours = OnlineHours + ( @OnlineMinutes / 60 ),
OnlineMinutes = ( @OnlineMinutes % 60 ) WHERE PlayerLogin = @PlayerLogin
END
与
UPDATE [dbo].[AccountTable] SET
OnlineHours = OnlineHours + ( @OnlineMinutes / 60 )
, OnlineMinutes = ( @OnlineMinutes % 60 )
WHERE PlayerLogin = @PlayerLogin AND OnlineMinutes >= 60
通过这些更改,您可以消除这两个表上的SELECT然后UPDATE模式。这对于避免死锁是一件好事。
最后,您应该遵循重新运行事务的建议。将所有三个更新语句放在包含在事务中的循环中,出现错误,回滚并让它再次尝试事务,当然成功时,您只需提交并退出循环。您仍然需要进行前两次更改,因为避免死锁比死锁后重试要好得多。
你需要在重试次数上设置一个相当小的上限(我建议不超过5次)。