问题
我试图弄清楚如何在数据库中正确设置事务,并考虑潜在的延迟。
设置
在我的示例中,我有一个 , 表users
,keys
其中每个用户可以有多个键,还有一个config
表,它指示每个用户可以拥有多少键。
我想运行一个存储过程:
- 确定是否允许给定用户请求密钥。
- 获取可用的、无人认领的密钥。
- 尝试为给定用户兑换密钥。
该过程的伪代码是:
START TRANSACTION
(1) CALL check_permission(...,@result);
IF (@result = 'has_permission') THEN
(2) SET @unclaimed_key_id = (QUERY FOR RETURNING AVAILABLE KEY ID);
(3) CALL claim_key(@unclaimed_key_id);
END IF;
COMMIT;
我遇到的问题是,当我在 step 之后模拟滞后时1
,(通过使用SELECT SLEEP(<seconds>)
),给定的用户可以通过在多个会话中运行该过程,在他们只有兑换一个密钥的权限时兑换多个密钥。第一个程序已经完成了它的睡眠(这也是模拟滞后)
这是表和过程的代码 (注意:对于这个小例子,我不关心索引和外键,但显然我在实际项目中使用它们)。
要查看我的问题,只需在数据库中设置表和过程,然后打开两个 mysql 终端,并首先运行:
CALL `P_user_request_key`(10,1,@out);
SELECT @out;
然后在第二次运行中快速(你有 10 秒):
CALL `P_user_request_key`(0,1,@out);
SELECT @out;
尽管配置中的最大值设置为每个用户 3 个,但两个查询都将成功返回key_claimed
,并且用户最终将分配给他 4 个键。Bob
问题
- 避免此类问题的最佳方法是什么?我正在尝试使用事务,但我觉得它不会专门帮助解决这个问题,并且可能会执行此错误。
- 我意识到解决问题的一种可能方法是将所有内容封装在一个大型更新查询中,但我宁愿避免这种情况,因为我喜欢能够设置单独的过程,其中每个过程只意味着做一个任务。
- 此示例背后的数据库旨在供许多(数千)并发用户使用。因此,如果一位用户尝试兑换代码不会阻止所有其他用户兑换代码,那将是最好的。如果另一个用户已经领取了密钥,我可以更改我的代码以再次尝试兑换,但绝对不应该发生用户只有获得一个权限的情况下可以兑换两个代码的情况。