简体   繁体   English

在SQL Server中更新时如何测试行被锁定

[英]How to test rows are locked while being updated in SQL Server

What is the best way to test if rows are being locked while they are being updated? 测试行在更新时是否被锁定的最佳方法是什么?

I have a query which select the top x records of a table and it updates them but multiple worker threads will be calling the same query so I want to ensure this are locked and if locked that it throws an error of some sort so that I can handle it accordingly but I don't seem to be able to throw an exception that I can catch in .NET (or in SQL for that matter). 我有一个查询,该查询选择一个表的前x个记录并更新它们,但是多个工作线程将调用同一查询,因此我想确保此查询已被锁定,如果被锁定,它会抛出某种错误,以便我可以相应地处理它,但我似乎无法抛出一个我可以在.NET(或SQL)中捕获的异常。

The query looks like: 查询看起来像:

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

I've tried to step through the debugger in SQL to just call the BEGIN TRANSACTION and then call the same query from my .NET application and expected an error but it just ran fine in .NET 我试图逐步调试SQL中的调试器,以仅调用BEGIN TRANSACTION ,然后从.NET应用程序中调用相同的查询,并期望出现错误,但在.NET中运行良好

So my question is how can I generate an error so that I know the records are currently being updated? 所以我的问题是如何生成错误,以便知道当前正在更新记录? I want to take a specific action when this occurs ie retry in x milliseconds for example. 发生这种情况时,我想采取特定的措施,例如以x毫秒重试。

Thanks. 谢谢。

Based on your recent comments (please add this information to your question body), you just want to make sure that each thread only “gets” rows that are not made available to the other threads. 根据您最近的评论(请将此信息添加到您的问题正文中),您只想确保每个线程仅“获取”其他线程无法使用的行。 Like picking tasks from a table of pending tasks, and making sure no task is picked up by two threads. 就像从待处理任务表中选择任务,并确保两个线程都没有选择任何任务。

You are overthinking the locking. 您正在考虑锁定。 Your problem is not something that requires fiddling with the SQL locking mechanism. 您的问题不是需要摆弄SQL锁定机制的问题。 You might fiddle with locking if you need to tune for performance reasons but you're far from being able to establish if that is needed. 如果您出于性能原因需要进行调优,则可能会尝试使用锁定,但是远不能确定是否需要这样做。 I wouldn't even bother with lock hints at this stage. 在此阶段,我什至不理会锁定提示。

What you want to do is have a field in the row that indicates whether the row has been taken, and by whom. 您想要做的是在行中有一个字段,该字段指示该行是否被占用,以及被谁占用。 Your made-up sample T-SQL doesn't use anything consistently, but the closest thing is the Z column. 组成的样本T-SQL并没有一致地使用任何内容,但最接近的是Z列。

You need to select rows that have a value of NULL (not taken yet) in Z . 您需要选择Z具有NULL值(尚未使用)的行。 You are clearly on that track already. 您显然已经步入正轨。 This field could be a BIT Yes/No and it can be made to work (look up the OUTPUT clause of UPDATE to see how you can pick up which rows were selected); 该字段可以是BIT是/否,并且可以使之起作用(查找UPDATE的OUTPUT子句以了解如何选择选定的行); but I suspect you will find far more useful for tracing/debugging purposes to be able to identify rows taken together by looking at the database alone. 但是我怀疑您会发现,对于跟踪/调试而言,仅通过查看数据库即可识别出合并在一起的行,这将更加有用。 I would use a column that can hold a unique value that cannot be used by any other thread at the same time. 我将使用一个可以包含唯一值的列,该值不能同时被任何其他线程使用。

There are a number of approaches. 有很多方法。 You could use a (client process) Thread ID or similar, but that will repeat over time which might be undesirable. 您可以使用(客户端进程)线程ID或类似名称,但是随着时间的推移会重复执行,这可能是不希望的。 You could create a SQL SEQUENCE and use those values, which has the nice feature of being incremental, but that makes the SQL a little harder to understand. 您可以创建一个SQL SEQUENCE并使用这些值,它具有递增的良好功能,但是这会使SQL变得更难理解。 I'll go with a GUID for illustration purposes. 我将使用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)

Now only your one thread can use those rows (assuming of course that no thread cheats and violates the code pattern). 现在,只有您的一个线程可以使用这些行(当然,假设没有线程欺骗并违反代码模式)。 Notice that I didn't even open an explicit transaction. 注意,我什至没有打开显式事务。 In SQL Server, UPDATEs are transactionally atomic by default (they run inside an implicit transaction). 在SQL Server中,默认情况下,UPDATE是事务性原子的(它们在隐式事务中运行)。 No thread can pick those rows again because no thread (by default) is allowed to update or even see those rows until the UPDATE has been committed in its entirety (for all rows). 没有线程可以再次选择这些行,因为在完全提交了UPDATE(对于所有行)之前,不允许任何线程(默认情况下)更新甚至查看这些行。 Any thread that tries to run the UPDATE at the same time either gets different unassigned rows (depending on your locking hints and how cleverly you select the rows to pick - that's part of the “advanced” performance tuning), or is paused for a few milliseconds waiting for the first UPDATE to complete. 任何试图同时运行UPDATE的线程都会获得不同的未分配行(取决于锁定提示以及您选择要选择的行的技巧-这是“高级”性能调整的一部分),或者被暂停了几行等待第一个UPDATE完成的毫秒数。

That's all you need. 这就是您所需要的。 Really. 真。 The rows are yours and yours only: 这些行只属于您自己:

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

Now your code gets the data from its dedicated rows, plus the unique ID which it can use later to reference the same rows, if you need to (take out the BatchId from the return set if you truly don't need it). 现在,您的代码从其专用行中获取数据,以及如果需要,可以在以后使用该ID引用同一行的唯一ID(如果确实不需要,请从返回集中取出BatchId)。

If my reading of what you are trying to do is correct, you will probably want to flag the rows when your code is done by setting another field to a flag value or a timestamp or something. 如果我对您尝试做的事情的阅读是正确的,则在代码编写完成后,您可能希望通过将另一个字段设置为标志值或时间戳等来标记行。 That way you will be able to tell if rows were “orphaned” because they were taken for processing but the process died for any reason (in which case they probably need to be made available again by setting Z to NULL again). 这样,您将能够确定行是否被“孤立了”,因为行是为了处理而行,但是由于任何原因该进程都死了(在这种情况下,可能需要通过再次将Z设置为NULL来使行再次可用)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM