所以这是我的场景,让我们假设我正在建立一个在线购物平台。我的用户在100
字段或表格中的余额为user_balance
。
现在,用户打开让他们withdrawal page
的{{1}}和允许他withdraw money
的{{1}}
假设用户提取100美元并同时购买100美元的手表。
我的问题是shopping page
会同时执行还是等待其他人完成选择。
如果buy a watch of 100 dollar with one click
同时执行,SELECT user_balance FROM balances FOR UPDATE
将为这两个页面显示SELECT...FOR UPDATE
,因此,它将允许提取user_balance
并购买手表对于100
因此,当我们最终更新用户的余额时,它将显示负余额
100
以下是两个页面的代码概念:
提款页面:
100
购买观察页面:
100(user balance) - 100(withdrawal amount) - 100(purchasing of watch) = -100
答案 0 :(得分:2)
这里的正确方法似乎是使用SELECT ... FOR UPDATE
在单独的事务中运行的每个操作。在伪代码中,撤销(或购买)的过程看起来像这样:
start transaction
SELECT user_balance FROM balances FOR UPDATE;
UPDATE balances SET user_balance = user_balance - 100;
end transaction
此模式的工作原理如下。该事务获得对正在更新的用户余额记录的独占锁定。这意味着在借记之前尝试读取用户余额的任何其他事务都将阻止,并且必须等待。这避免了两个事务交错的情况,导致不正确的平衡。
请注意,锁定读取需要InnoDB引擎。检查MySQL documentation以获取更多信息。
答案 1 :(得分:1)
SELECT FOR UPDATE会延迟读取吗?
是。您需要使用交易来获得良好的结果。
SELECT ... FOR UPDATE
,当在InnoDB表中的MySQL事务内部完成时,锁定所选的一行或多行。让我们假设您的代码实际上只选择一行,SELECT something FROM balances WHERE id=something FOR UPDATE
。
然后,如果连接到MySQL的两个不同程序尝试在同一行上大约同时执行SELECT
,则其中一个将获胜。也就是说,它将首先到达那里,然后查询将完成。
要使其正常运行,请在START TRANSACTION
和COMMIT
中包含您需要完成的所有工作。 START TRANSACTION
之后应该做的第一件事应该是你的SELECT ... FOR UPDATE
。
如果在您完成工作时决定用户无法执行其操作,您可以发出ROLLBACK
代替COMMIT
,并且将放弃交易中的所有更改
在第一个程序执行COMMIT
完成其交易之前,第二个程序的查询将无法完成。然后它将读取在该事务期间存储在表中的任何内容。
这些是要记住的事项:SQL事务看起来,连接到表服务器的其他程序,就像它们一次发生的那样。当一个程序正在进行事务时,其他程序会等待。大部分时间交易都很快完成,因此很难观察等待时间。