What\'s the common way to deal with concurrent updates in an SQL database ?
Consider a simple SQL schema(constraints and defaults not shown..) like
c
You could set up a queueing mechanism where additions to or subtractions from a rank type value would get queued up for periodic LIFO processing by some job. If real-time info on a rank's "balance" is required this wouldn't fit because the balance wouldn't compute until the outstanding queue entries are reconciled, but if it's something that doesn't require immediate reconciliation it might serve.
This seems to reflect, at least on the outside looking in, how games like the old Panzer General series handle individual moves. One player's turn comes up, and they declare their moves. Each move in turn is processed in sequence, and there are no conflicts because each move has its place in the queue.
Use transactions:
BEGIN WORK;
SELECT creds FROM credits WHERE userid = 1;
-- do your work
UPDATE credits SET creds = 150 WHERE userid = 1;
COMMIT;
Some important notes:
Combining transactions with SQL stored procedures can make the latter part easier to deal with; the application would just call a single stored procedure in a transaction, and re-call it if the transaction aborts.
There are is one critical point in your case when you decrease user`s current credit field by a requested amount and if it decreased successfully you do other operations and problem is in theory there can be many parallel requests for decrease operation when for example user has 1 credits on balance and with 5 parallel 1 credit charge requests he can purchase 5 things if request will be sent exactly on the same time and you end up with -4 credits on user`s balance.
To avoid this you should decrease current credits value with requested amount (in our example 1 credit) and also check in where if current value minus requested amount is more or equal to zero:
UPDATE credits SET creds = creds-1 WHERE creds-1>=0 and userid = 1
This will guaranty that user will never purchase many things under few credits if he will dos your system.
After this query you should run ROW_COUNT() which tells if current user credit met criteria and row was updated:
UPDATE credits SET creds = creds-1 WHERE creds-1>=0 and userid = 1
IF (ROW_COUNT()>0) THEN
--IF WE ARE HERE MEANS USER HAD SURELY ENOUGH CREDITS TO PURCHASE THINGS
END IF;
Similar thing in a PHP can be done like:
mysqli_query ("UPDATE credits SET creds = creds-$amount WHERE creds-$amount>=0 and userid = $user");
if (mysqli_affected_rows())
{
\\do good things here
}
Here we used nor SELECT ... FOR UPDATE neither TRANSACTION but if you put this code inside transaction just make sure that transaction level always provides most recent data from row (including ones other transactions already committed). You also can user ROLLBACK if ROW_COUNT()=0
Downside of WHERE credit-$amount>=0 without row locking are:
After update you surely know one thing that user had enough amount on credit balance even if he tries yo hack credits with many requests but you dont know other things like what was credit before charge(update) and what was credit after charge(update).
Caution:
Do not use this strategy inside transaction level which does not provide most recent row data.
Do not use this strategy if you want to know what was value before and after update.
Just try to rely on fact that credit was successfully charged without going below zero.