锁定Postgres以进行UPDATE / INSERT组合


11

我有两张桌子。一个是日志表;另一个是日志表。另一个实质上包含只能使用一次的优惠券代码。

用户需要能够兑换优惠券,这将在日志表中插入一行并将优惠券标记为已使用(通过将used列更新为true)。

自然,这里存在明显的比赛条件/安全性问题。

在过去的mySQL世界中,我做过类似的事情。在那个世界中,我将全局锁定两个表,在一次只能执行一次的前提下,确保逻辑安全,然后在完成后解锁表。

Postgres中有更好的方法吗?特别是,我担心锁是全局的,但不一定是全局的-我真的只需要确保没有其他人试图输入该特定代码,那么也许行级锁会起作用吗?

Answers:


15

我以前听说过类似MySQL的并发问题。在Postgres中并非如此。

默认READ COMMITTED事务隔离级别中的内置行级锁定就足够了。

我建议使用一条带有修改数据的CTE的语句(MySQL也没有),因为将一个表中的值直接传递给另一个表很方便(如果需要的话)。如果您不需要coupon表中的任何内容,则也可以使用带有单独UPDATEINSERT语句的事务。

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同一行,它将看到该行上的锁并等待阻塞事务完成(ROLLBACKCOMMIT),然后成为锁队列中的第一个:

  • 如果已提交,请重新检查条件。如果还是NOT used,请锁定该行并继续。否则,UPDATE现在找不到任何符合条件的行,并且不执行任何操作,不返回任何行,因此the INSERT也不执行任何操作。

  • 如果回滚,请锁定该行并继续。

一个竞争条件没有潜力

一个没有潜在的死锁,除非你把更多的写入同一交易或其他方式锁定不仅仅是多了一个行。

INSERT是无忧无虑。如果由于某种原因coupon_idlog表中已经有该表(并且您在上具有UNIQUE或PK约束log.coupon_id),则在发生唯一违规后将回滚整个事务。将在您的数据库中指示非法状态。如果上述语句是写入log表的唯一方法,则永远不会发生这种情况。


多个事务试图兑现相同的代码确实是一件罕见的事情,但是您的猜想是正确的,因为这将完全是有人尝试使用该系统的时候。非常感谢— CTE对我而言是转向Postgres的一大吸引力,但我没有意识到隐式锁定足以胜任这一工作。
罗布·米勒
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.