You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SELECT...FOR UPDATE是否延迟读取?商城余额并发操作技术咨询

关于并发操作中SELECT...FOR UPDATE的行为解释

嘿,这个并发场景确实是在线支付/购物平台里非常典型的问题,我来给你拆解清楚你的两个疑问:

1. SELECT...FOR UPDATE是否会实际延迟读取?

其实不能叫“延迟读取”,更准确的说法是它会通过行级锁阻塞后续对同一行的读写操作,直到当前事务释放锁。当第一个事务执行SELECT user_balance FROM balances FOR UPDATE时,数据库会锁定该用户对应的balances行;此时第二个事务执行同样的语句时,会进入等待状态——直到第一个事务提交(或回滚)释放锁,第二个事务才能读取到最新的余额数据,而不是初始的100。

2. 两条SELECT...FOR UPDATE语句是否会同时执行?

不会,它们是串行执行的。具体流程是:

  • 假设提现请求先到达,执行SELECT...FOR UPDATE后成功锁定用户的余额行,读取到初始余额100;
  • 此时购表请求的SELECT...FOR UPDATE会被阻塞,无法读取余额,必须等提现事务完成(提交或回滚);
  • 如果提现操作完成后余额被更新为0,那么购表请求的SELECT...FOR UPDATE会读取到0,此时判断0 > 100不成立,就不会执行购表的更新操作,从根源上避免了余额变成负数的情况。

补充:你的代码需要注意的关键点

一定要确保SELECT...FOR UPDATE和后续的UPDATE操作在同一个数据库事务中执行!如果你的代码只是单独执行SELECT...FOR UPDATE,之后再执行UPDATE却没包裹在事务里,那么锁会在SELECT执行完成后立刻释放,这样还是会出现并发问题。正确的流程示例(伪代码):

// 提现页面事务包裹示例
$pdo->beginTransaction();
try {
    $withdrawal_amount = 100;
    $stmt = $pdo->prepare("SELECT user_balance FROM balances WHERE user_id = ? FOR UPDATE");
    $stmt->execute([$user_id]);
    $user_balance = $stmt->fetchColumn();
    
    if ($user_balance >= $withdrawal_amount) { 
        $update_stmt = $pdo->prepare("UPDATE balances SET user_balance = user_balance - ? WHERE user_id = ?");
        $update_stmt->execute([$withdrawal_amount, $user_id]);
    }
    $pdo->commit();
} catch (Exception $e) {
    $pdo->rollBack();
    // 处理异常逻辑
}

这样就能保证整个“读取-判断-更新”的操作是原子性的,彻底杜绝并发导致的余额超支问题。

内容的提问来源于stack exchange,提问作者dramasea

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:22:27