【问题标题】:Does SELECT....FOR UPDATE actually delay the read?SELECT..FOR UPDATE 实际上会延迟读取吗?
【发布时间】:2018-06-16 04:20:48
【问题描述】:

所以这是我的场景,假设我正在制作一个在线购物平台。并且我的用户在user_balance 字段或表中有100 的余额。

现在,用户,打开withdrawal page 让他们withdraw moneyshopping page 让他到buy a watch of 100 dollar with one click

假设用户提现 100 美元,同时以 100 美元购买手表。

我的问题是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

这是两个页面的代码概念:

提款页​​面:

$withdrawal_amount = 100;

$user_balance = "SELECT user_balance FROM balances FOR UPDATE"; //actually return 100?(not sure about it, that is what my question about)

if($user_balance > $withdrawal_amount){
     //allow withdrawal
     $update_sql_query = "UPDATE balances SET user_balance = user_balance - " . $withdrawal_amount;
}

购买观看页面:

   $product_subtotal = 100;

    $user_balance = "SELECT user_balance FROM balances FOR UPDATE"; //actually return 100?(not sure about it, that is what my question about)

    if($user_balance > $product_subtotal){
         //allow withdrawal
         $update_sql_query = "UPDATE balances SET user_balance = user_balance - " . $product_subtotal;
    }

【问题讨论】:

  • 快速回答,由于并发,mysql 应该会阻止您出现 -100。有关更多详细信息,请阅读此stackoverflow.com/questions/4828490/… 链接,其中讨论了 Mysql 中的并发
  • @SteveB 您好,感谢您的评论,我已经更新了我的问题,我不确定两个页面的 user_balance 是否相同。如果是,我该怎么做才能防止这种情况发生
  • 到目前为止提供的答案并未解决您的最后一个问题。防止两个页面显示相同信息的唯一方法是只允许一次登录。另一种选择是让页面自动更新,因此一旦值更改,第二个页面也会更新,可能使用推送功能。
  • @SteveB 如果两者都在前端显示相同的信息,则可以。我唯一担心的是后端出现问题时

标签: php mysql sql


【解决方案1】:

这里的正确方法似乎是让每个操作使用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 了解更多信息。

【讨论】:

    【解决方案2】:

    SELECT FOR UPDATE 会延迟读取吗?

    是的。你需要使用事务来获得好的结果。

    SELECT ... FOR UPDATE,当在 InnoDB 表中的 MySQL 事务中完成时,锁定选定的行。假设您的代码实际上只选择了一行,通过执行SELECT something FROM balances WHERE id=something FOR UPDATE

    然后,如果连接到 MySQL 的两个不同程序尝试在大致相同的时间在同一行上执行 SELECT,其中一个将获胜。也就是说,它会先到达那里,然后查询就会完成。

    要使其正常工作,请将您需要做的所有工作封装在 START TRANSACTIONCOMMIT 中。在START TRANSACTION 之后你应该做的第一件事应该是你的SELECT ... FOR UPDATE

    如果您在工作时决定用户不能做她想做的事,您可以发出ROLLBACK 代替COMMIT,交易中的所有更改都将被放弃。

    在第一个程序执行COMMIT 完成其事务之前,第二个程序的查询不会完成。然后它将读取在该事务期间存储到表中的任何内容。

    这些是要记住的事情:SQL 事务看起来,对于连接到表服务器的其他程序来说,就像它们同时发生一样。当一个程序正在进行事务时,其他程序会等待。大多数情况下事务很快完成,因此很难观察到等待时间。

    【讨论】:

    • 感谢您的回答,所以我也需要包括(开始交易),如下面的答案所示?或者只是选择...进行更新就可以了
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-03-10
    • 2018-12-17
    • 2012-07-06
    • 2018-01-20
    • 2011-06-28
    • 2012-11-10
    • 1970-01-01
    相关资源
    最近更新 更多