I keep track of user credits in a creditlog table that looks like this:
user quantity action balance
1001 20 SEND 550
1001 30 SEND 520
1001 5 SEND 515
Now at first I tried to use Active Record syntax and select latest balance then insert a new line that computed the new balance. Then I found myself into a race condition:
user quantity action balance
1001 20 SEND 550
1001 30 SEND 520
1001 5 SEND 545 (the latest balance was not picked up because of a race condition)
Next solution was using a single query to do both:
INSERT INTO creditlog (action, quantity, balance, memberId)
VALUES (:action, :quantity, (SELECT tc.balance from creditlog tc where tc.memberId=:memberId ORDER by tc.id desc limit 1) - :quantity, :memberId);
My script which tests this with 10 reqs/second would throw the following error for 2/10 queries:
SQLSTATE[40001]: Serialization failure: 1213 Deadlock found when trying to get lock; try restarting transaction. The SQL statement executed was:
INSERT INTO creditlog (action, quantity, reference, balance, memberId)
VALUES (:action, :quantity, :reference, (SELECT balance from (SELECT tm.* FROM creditlog tm where tm.memberId=:memberId) tc where tc.memberId=:memberId ORDER by tc.id desc limit 1) -:quantity, :memberId, :recipientId);.
Bound with :action='send', :quantity='10', :reference='Testing:10', :memberId='10001043'.
Shouldn't the engine wait for the first operation to release the table then start on the second one?
Does my issue relate to: How to avoid mysql 'Deadlock found when trying to get lock; try restarting transaction' ?
How can I avoid this situation and turn the concurrent requests into sequential operations?