Answers:
我以前听说过类似MySQL的并发问题。在Postgres中并非如此。
默认READ COMMITTED
事务隔离级别中的内置行级锁定就足够了。
我建议使用一条带有修改数据的CTE的语句(MySQL也没有),因为将一个表中的值直接传递给另一个表很方便(如果需要的话)。如果您不需要coupon
表中的任何内容,则也可以使用带有单独UPDATE
和INSERT
语句的事务。
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;
一次以上的交易试图兑现同一张优惠券应该是一件罕见的事情。他们有一个唯一的号码,不是吗?但是,在同一时间尝试进行的多个交易应该很少见。(也许是应用程序错误或有人尝试玩系统?)
不管怎样,UPDATE
唯一成功的交易只有一次。在更新之前,A UPDATE
获取每个目标行上的行级别锁。如果并发事务尝试到UPDATE
同一行,它将看到该行上的锁并等待阻塞事务完成(ROLLBACK
或COMMIT
),然后成为锁队列中的第一个:
如果已提交,请重新检查条件。如果还是NOT used
,请锁定该行并继续。否则,UPDATE
现在找不到任何符合条件的行,并且不执行任何操作,不返回任何行,因此the INSERT
也不执行任何操作。
如果回滚,请锁定该行并继续。
有一个竞争条件没有潜力。
有一个没有潜在的死锁,除非你把更多的写入同一交易或其他方式锁定不仅仅是多了一个行。
该INSERT
是无忧无虑。如果由于某种原因coupon_id
该log
表中已经有该表(并且您在上具有UNIQUE或PK约束log.coupon_id
),则在发生唯一违规后将回滚整个事务。将在您的数据库中指示非法状态。如果上述语句是写入log
表的唯一方法,则永远不会发生这种情况。