首页 > 解决方案 > 如何测试行在 SQL Server 中更新时被锁定

问题描述

测试行在更新时是否被锁定的最佳方法是什么?

我有一个查询,它选择表的前 x 条记录并更新它们,但多个工作线程将调用相同的查询,所以我想确保它被锁定,如果锁定它会引发某种错误,以便我可以相应地处理它,但我似乎无法抛出我可以在.NET(或在 SQL 中)捕获的异常。

查询如下所示:

BEGIN TRANSACTION

UPDATE MyTable WITH (ROWLOCK)
SET x = @X,
    y = @Y,
WHERE ID IN (SELECT TOP 50 ID 
             FROM MyTable
             WHERE Z IS NULL)

SELECT TOP 50 x 
FROM MyTable
WHERE x = @W

COMMIT TRANSACTION

我试图通过 SQL 中的调试器单步调用BEGIN TRANSACTION,然后从我的 .NET 应用程序调用相同的查询并预期会出现错误,但它在 .NET 中运行良好

所以我的问题是如何生成错误以便我知道记录当前正在更新?我想在发生这种情况时采取特定的措施,例如在 x 毫秒内重试。

谢谢。

标签: c#sql.netsql-server

解决方案


根据您最近的评论(请将此信息添加到您的问题正文中),您只想确保每个线程仅“获取”其他线程无法使用的行。就像从挂起的任务表中挑选任务,并确保没有任务被两个线程拾取。

你在考虑锁定。您的问题不是需要摆弄 SQL 锁定机制的问题。如果出于性能原因需要进行调整,您可能会摆弄锁定,但您远无法确定是否需要这样做。在这个阶段,我什至不会打扰锁定提示。

您要做的是在行中有一个字段,指示该行是否已被占用,以及由谁占用。您编造的示例 T-SQL 没有始终如一地使用任何东西,但最接近的是Z列。

您需要在Z. 你显然已经在那个轨道上了。该字段可以是 BIT Yes/No 并且可以使其工作(查看 UPDATE 的 OUTPUT 子句以查看如何获取选择了哪些行);但我怀疑您会发现对于跟踪/调试目的更有用的是能够通过单独查看数据库来识别一起使用的行。我会使用一个可以保存一个唯一值的列,该值不能被任何其他线程同时使用。

有多种方法。您可以使用(客户端进程)线程 ID 或类似的,但这会随着时间的推移而重复,这可能是不可取的。您可以创建一个 SQL SEQUENCE 并使用这些值,它具有增量的优点,但这会使 SQL 更难理解。出于说明目的,我将使用 GUID。

DECLARE @BatchId AS UNIQUEIDENTIFIER
SET @BatchId = NEWID()
UPDATE MyTable
SET x = @X,
    y = @Y,
    Z = @BatchId
WHERE ID IN (SELECT TOP 50 ID 
             FROM MyTable
             WHERE Z IS NULL)

现在只有你的一个线程可以使用这些行(当然假设没有线程作弊和违反代码模式)。请注意,我什至没有打开显式事务。在 SQL Server 中,UPDATE 在默认情况下是事务原子的(它们在隐式事务中运行)。没有线程可以再次选择这些行,因为在 UPDATE 全部提交(对于所有行)之前,不允许线程(默认情况下)更新甚至查看这些行。任何尝试同时运行 UPDATE 的线程要么获得不同的未分配行(取决于您的锁定提示以及您选择要选择的行的巧妙程度 - 这是“高级”性能调整的一部分),或者暂停了一些等待第一个 UPDATE 完成的毫秒数。

这就是你所需要的。真的。这些行是你的,只有你的:

SELECT @BatchId AS BatchId, x 
FROM MyTable
WHERE Z = @BatchId

现在,您的代码从其专用行中获取数据,加上唯一 ID,如果您需要,它可以在以后使用它来引用相同的行(如果您确实不需要,请从返回集中取出 BatchId)。

如果我对您正在尝试做的事情的解读是正确的,那么您可能希望在代码完成时通过将另一个字段设置为标志值或时间戳或其他内容来标记行。这样,您将能够判断行是否“孤立”,因为它们被用于处理但进程因任何原因而死亡(在这种情况下,它们可能需要通过再次设置Z为 NULL 再次使其可用)。


推荐阅读