Using SQL dB column as a lock for concurrent opera

2019-08-30 09:33发布

问题:

We have a long running user operation that is handled by a pool of worker processes. Data input and output is from Azure SQL.

The master Azure SQL table structure columns are approximated to

[UserId, col1, col2, ... , col N, beingProcessed, lastTimeProcessed ] 

beingProcessed is boolean and lastTimeProcessed is DateTime. The logic in every worker role is as shown below and with multiple workers processing (each with their own Entity Framework layer), in essence beingProcessed is being used a lock for MutEx purposes

Question: How can I deal with concurrency issues on the beingProcessed "lock" itself based on the above load? I think read-modify-write operation on the beingProcessed needs to be atomic but I'm open to other strategies. Open to other code refinements too.

[Update]: I wonder if TransactionScope is what's needed here ... http://msdn.microsoft.com/en-US/library/system.transactions.transactionscope(v=vs.110).aspx

Code:

public void WorkerRoleMain()
{
    while(true)
    {
        try
        {
            dbContext db = new dbContext();

            // Read
            foreach (UserProfile user in db.UserProfile
                    .Where(u => DateTime.UtcNow.Subtract(u.lastTimeProcessed) 
                            > TimeSpan.FromHours(24) & 
                            u.beingProcessed == false))
            {
                user.beingProcessed = true; // Modify
                db.SaveChanges();           // Write
                // Do some long drawn processing here
                ...
                ...
                ...
                user.lastTimeProcessed = DateTime.UtcNow;
                user.beingProcessed = false;
                db.SaveChanges();
            }
        }
        catch(Exception ex)
        {
            LogException(ex);
            Sleep(TimeSpan.FromMinutes(5));
        }
    } // while ()
}

回答1:

What we usually do is this:

At the beginning of a long operation we start a transaction:

BEGIN TRANSACTION

Then we select a row from the table we would like to update/delete using these hints:

SELECT * FROM Table WITH (ROWLOCK, NOWAIT) Where ID = 123;

Then we check that we have the row. If the row is locked by another process there will be an SQL Error. In this case we rollback the transaction and advise the user. If the record is locked we process the record, and do the required updates, using the same transaction object we used to lock the record:

UPDATE Table SET Col1='value' WHERE ID = 123;

Then we COMMIT the transaction.

COMMIT;

This is just the Pseudo-code of the process. You will have to implement it in your program.

One small note regarding the above process. When you lock the record in SQL Server (or Azure), use the primary key in your WHERE Clause, otherwise the SQL Server will decide to use a Page lock, or Table lock