I have heard of concurrency problems like that in MySQL before. Not so in Postgres.
Built-in row-level locks in the default READ COMMITTED
transaction isolation level are enough.
I suggest a single statement with a data-modifying CTE (something that MySQL also doesn't have) because it's convenient to pass values from one table to the other directly (if you should need that). If you don't need anything from the coupon
table you can use a transaction with separate UPDATE
and INSERT
statements just as well.
WITH upd AS (
UPDATE coupon
SET used = true
WHERE coupon_id = 123
AND NOT used
RETURNING coupon_id, other_column
)
INSERT INTO log (coupon_id, other_column)
SELECT coupon_id, other_column FROM upd;
It should be a rare thing that more than one transaction tries to redeem the same coupon. They have a unique number, don't they? More than one transaction trying at the same moment in time should be much rarer, yet. (Maybe an application bug or somebody trying to game the system?)
Be that as it may, the UPDATE
only succeeds for exactly one transaction, no matter what. An UPDATE
acquires a row level lock on each target row before updating. If a concurrent transaction tries to UPDATE
the same row, it will see the lock on the row and wait till the blocking transaction is finished (ROLLBACK
or COMMIT
), then being the first in the lock queue:
If committed, recheck the condition. If it's still NOT used
, lock the row and proceed. Else the UPDATE
now finds no qualifying row and does nothing, returning no row, so the INSERT
also does nothing.
If rolled back, lock the row and proceed.
There is no potential for a race condition.
There is no potential for a deadlock unless you put more writes into the same transaction or otherwise lock more rows than just the one.
The INSERT
is care-free. If, by some mistake the coupon_id
already is in the log
table (and you have a UNIQUE or PK constraint on log.coupon_id
), the whole transaction will be rolled back after a unique violation. Would indicate an illegal state in your DB. If the above statement is the only way to write to the log
table, that should never occur.
Best Answer
During
...
, any other thread trying to doGET_LOCK('rick')
would be blocked.Will that work for your application?
Be aware of the caveats: A disconnect (eg, network glitch) will release the lock. Multiple locks need some care. Etc.