简体   繁体   中英

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).

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

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.

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. 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.

You need to select rows that have a value of NULL (not taken yet) in Z . 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); 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. 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. I'll go with a GUID for illustration purposes.

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). 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). 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.

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).

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).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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