|
|
|
|
|
by CraigJPerry
968 days ago
|
|
If transaction 1 and 2 are running the example and the ordering is something like: txn1 = db.begin()
# implicit exclusive row lock established here on A row, exclusive rather than shared read because we say for update
currBalance1 = txn1.query('select balance from accounts where name = A for update’)
… <snipped some of the example code> …
txn1.execute('update accounts set balance = balance - $amount where name = A')
# Concurrently tx2 begins, tx1 has locked row A so far
txn2 = db.begin()
# we’re exclusively locking row B in txn2
currBalance2 = txn2.query('select balance from accounts where name = B for update’)
# This is the first point in transaction 1 where we take an exclusive lock on B, however we are deadlocked now
txn1.execute('update accounts set balance = balance + $amount where name = B')
The solution is what the parent suggested, take the exclusive locks on rows A&B at the same time because if transaction 2 is locking B&C you can’t afford to do 2 separate for update calls because b might get locked as you’ve just locked A and were about to lock B select balance from accounts where name = 'A' or name = ‘B’ for update
|
|
By locking the accounts in smallest->largest name (or id), you can afford to do 2 separate for update calls. Even when done in a single statement specify the canonical ordering of locks to avoid deadlocks. For example, 3 transactions locking (A, B), (B, C), (C, A):
This is the lock order regardless of which of A/B/C is being debited or credited. As a final detail, make sure the ORDER BY that you specify is on a unique index or else there could/will be range locks on the index and won't strictly be using row locks.