Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Block a row from reads whilst sp executes

I have many .NET processes reading messages from an SQL Server 2008 DB table and processing them one at a time. I implement a simple SP to 'lock' the row that is being read by any one process, to avoid any two processes processing the same row.

BEGIN TRAN

SELECT @status = status FROM t WHERE t.id = @id
IF @status = 'L'
BEGIN
    -- Already locked by another process. Skip this
    RETURN 0
END
ELSE
    UPDATE t SET status = 'L' WHERE id = @id
    RETURN 1
END

COMMIT

However, this is flawed: sometimes a row gets 'locked' and processed twice. I suspect there is a concurrency problem: two processes reading the status before one updates it.

I think this could be resolved by implementing a read block somehow (i.e make the transaction block READs), but I am unsure how to do this. Can anyone help?

Thanks a lot in advance

Ryan

like image 872
Ryan Avatar asked Jul 29 '10 13:07

Ryan


People also ask

How do I lock a row in MySQL?

Record Locks A record lock is a lock on an index record. For example, SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE; prevents any other transaction from inserting, updating, or deleting rows where the value of t. c1 is 10 . Record locks always lock index records, even if a table is defined with no indexes.

What is optimistic locking in database?

Optimistic locking is a technique for SQL database applications that does not hold row locks between selecting and updating or deleting a row. The application is written to optimistically assume that unlocked rows are unlikely to change before the update or delete operation.

Does select for update block read?

A SELECT ... FOR UPDATE reads the latest available data, setting exclusive locks on each row it reads. Thus, it sets the same locks a searched SQL UPDATE would set on the rows.


2 Answers

Why not just replace the whole thing with

UPDATE t SET status = 'L' WHERE id = @id and status <> 'L'
RETURN @@ROWCOUNT

This will avoid 2 table accesses and holding locks open for any longer than necessary.

like image 143
Martin Smith Avatar answered Oct 17 '22 22:10

Martin Smith


Did you try using:

SELECT @status = status FROM t (UPDLOCK) WHERE t.id = @id

Refer to this link for more details.

The thing you're missing is that a SELECT statement doesn't normally lock the row, so if one process has executed the SELECT but hasn't execute the UPDATE yet, but then another process comes along and executes the SELECT, then it's going to return the row back since you haven't locked it.

By using the UPDLOCK, you lock that row with your SELECT statement and prevent the other process from getting it back until the first process commits the transaction, which is the behavior you want.

EDIT Of course, doing it with one statement as Martin suggests is the easiest way and you avoid having to deal with the locking issue at all.

like image 2
dcp Avatar answered Oct 17 '22 23:10

dcp