Jon*_*nah 8 postgresql locking knex.js
考虑一个rewards表,它有一type列,其中一个可能的值是ONE_PER_PERSON.
还有一个redeemed_rewards链接表,用于跟踪哪些用户兑换了哪些奖励.它有两列:user_id和reward_id.
现在考虑一个负责赎回奖励的业务逻辑的高级职能.该函数的签名如下所示:
function redeemReward(userId, rewardId)
Run Code Online (Sandbox Code Playgroud)
具体而言,考虑ONE_PER_PERSON奖励被兑换的情况.在高级别,该案例的函数逻辑如下:
查询以确保此用户之前未兑换奖励.也就是说,确保以下查询返回0的计数:
SELECT COUNT(*) FROM redeemed_rewards
WHERE user_id = ${userId} AND reward_id = ${rewardId}
Run Code Online (Sandbox Code Playgroud)假设没有,请插入兑换的奖励:
INSERT INTO redeemed_rewards VALUES (${userId}, ${rewardId})
Run Code Online (Sandbox Code Playgroud)提交交易
这种逻辑的问题在于它很容易受到竞争条件的影响.由于理论上可以通过多个线程调用该函数,因此可以想到2个线程都可以绕过步骤2,每个线程在另一个线程到达步骤4之前,导致两个插入的记录,从而违反ONE_PER_PERSON约束.
我认为正确的解决方案是在步骤1中锁定表格,并将其锁定直到第4步.
我的问题是:
对我来说,独特的指数不是一个可行的解决方案.并非所有类型的奖励都是每个用户一个.另外,我特别想了解postgres锁定以及如何在需要它的情况下正确使用它.
这提出了一个问题,因为没有某种显式锁定,重复插入有点难以防止.
第一种选择是使用奖励ID或用户作为信号量,以有效地序列化您对特定用户或奖励ID的所有写入.通过这种方式,你可以这样做:
SELECT * FROM reward WHERE id = ? FOR UPDATE;
Run Code Online (Sandbox Code Playgroud)
然后,每次兑换相同奖励的尝试都会等待其他尝试先通过.您可以根据您的流量方法对用户执行相同的操作.然后,当事务完成时,锁被释放.
这通过锁定交易的奖励行来工作.这里的主要优点是它很简单.主要缺点是只有您的应用程序才能以这种方式序列化读取.
通过这种方式,许多人可以同时兑换不同的奖励ID,但是对于每个奖励ID,它将锁定奖励表中的行并等待阻止其他人执行相同的操作,直到它提交为止.
第二种方法是使用咨询锁.这里有一些优点,但也有一些缺点,所以如果行锁不能满足你的需要,我会看一下(并花一些时间阅读文档).
编辑:
实际上可能有一个唯一的索引,但要做到这一点,你必须重新考虑一下你的数据库.正如您所提到的,(user_id, reward_id)组合对于某些奖励类型是唯一的.
因此,您需要做的是在奖励表上创建一个唯一索引reward_id, reward_type,然后在redeemed_rewards 表中将reward_type添加到您的外键中.
CREATE UNIQUE INDEX redeemed_rewards_only_one_per_user
ON redeemed_rewards (user_id, reward_id)
WHERE reward_type = 'ONE_PER_USER`
Run Code Online (Sandbox Code Playgroud)
当只有这些类型的奖励被赎回多次时,PostgreSQL会抛出一个错误.这样做的好处是可以使用db直接强制执行逻辑,因此您不必担心使用杂散查询或管理操作搞乱.
| 归档时间: |
|
| 查看次数: |
1300 次 |
| 最近记录: |