Problem:
I have a multi-threaded application (or multiple client applications) needing to access a "counter" stored in the database. They need to read the value, then +1 to the counter, and store it again. During the read and write, there is an additional layer of logic where the counter value is compared with an input obtained externally, and the result of the logic dictates whether to +1 to the counter or not.
Pseudocode:
var counter = Database.GetCurrentValueOfCounter();
counter = max(counter, input);
Database.Save(counter + 1);
return counter;
For example, in a multiple client scenario, all clients get an input
externally, which all equals the same value (since they are obtained at the same time).
When the first client enters this function, the other clients should wait until the counter is updated before entering. Thus for multiple clients in the above scenario, each would obtain a sequential counter
value.
What I've tried
I am using c# EF Core to implement the code and database logic. I have tried to use a serialisable transaction via Database.BeginTransaction(IsolationLevel.Serializable)
.
Then SELECT counter from Table
-> logic in c# -> UPDATE Table SET counter ...
However, this approach gives me a deadlock transaction. Researching deadlocks, I believe the deadlock occurs on the second thread SELECT
statement, which makes sense, since the first thread would be locking it in the transaction.
Question
What is the correct way to implement this locking/queuing mechanism on the database level?
Currently, I have resorted to entered a critical section in the code via lock(object)
, however, this WILL FAIL in a multi-server scenario. I can look into using SQL Distributed locks, however, it doesn't feel right to be doing the lock at the application level.
Can anyone point me to how I can achieve this sequential locking at the database level?