此SQL(从c#调用)偶尔会导致死锁。 服务器负载不是很大,所以使用的方法是尽可能锁定。
-- Lock to prevent race-conditions when multiple instances of an application calls this SQL:
BEGIN TRANSACTION
-- Check that no one has inserted the rows in T1 before me, and that T2 is in a valid state (Test1 != null)
IF NOT EXISTS (SELECT TOP 1 1 FROM T1 WITH(HOLDLOCK, TABLOCKX) WHERE FKId IN {0}) AND
NOT EXISTS(SELECT TOP 1 1 FROM T2 WITH(HOLDLOCK, TABLOCKX) WHERE DbID IN {0} AND Test1 IS NOT NULL)
BEGIN
-- Great! Im the first - go insert the row in T1 and update T2 accordingly. Finally write a log to T3
INSERT INTO T1(FKId, Status)
SELECT DbId, {1} FROM T2 WHERE DbId IN {0};
UPDATE T2 SET LastChangedBy = {2}, LastChangedAt = GETDATE() WHERE DbId IN {0};
INSERT INTO T3 (F1, FKId, F3)
SELECT {2}, DbId, GETDATE() FROM T2 WHERE DbId IN {0} ;
END;
-- Select status on the rows so the program can evaluate what just happened
SELECT FKId, Status FROM T1 WHERE FkId IN {0};
COMMIT TRANSACTION
我认为问题是需要锁定多个表。
我有点不确定这些表是否实际上是xlocked - 第一次使用表时 - 或者所有表都是在BEGIN TRANS上一次锁定?
答案 0 :(得分:3)
使用表锁可以增加死锁的可能性...并非所有死锁都是由于无序操作引起的......有些可能是由于其他仅尝试锁定单个活动而导致(如您所见)在完全锁定的同一个表中记录,因此锁定整个表会增加发生冲突的可能性。使用可序列化隔离级别时,范围锁定放在索引行上,这可以防止其他sql操作的插入/删除,其方式可能导致来自同一过程的两个并发操作导致死锁,即使它们被编码为执行其操作顺序相同......
无论如何,要找出导致死锁的确切原因,请设置SQL Server跟踪标志1204和1222.这些将导致有关每个死锁的详细信息写入SQL Server日志,包括涉及的语句。
Here是关于如何执行此操作的好文章。
(当你完成时别忘了关闭这些标志......)
答案 1 :(得分:1)
锁定在您调用lock或select with lock并在提交或回滚时释放时完成。
如果另一个程序先在T3中锁定,之后在T1或T2中锁定,则可能会死锁。然后两个事务正在等待彼此获取资源,同时锁定对方需要的内容。
您还可以避免表锁并使用可序列化的隔离级别。
答案 2 :(得分:0)
锁定的问题在于你真的需要同时查看你锁定的所有地方,没有办法将问题分离并拆分成许多较小的问题并单独查看。
例如,如果某些其他代码锁定相同的表,但没有明显的,并且顺序错误怎么办?那会导致僵局。
您需要在发现死锁时分析服务器状态,以尝试确定此刻正在运行的其他内容。只有这样才能尝试解决它。