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

PHP API并发请求返回重复手机号问题求助

解决高并发下API重复返回手机号的问题

你的问题核心在于:虽然开启了事务,但普通的SELECT语句是快照读(InnoDB默认隔离级别下),不会对读取的行加锁,导致多个并发事务同时读取到同一条未删除的手机号记录,进而重复返回并删除。

解决方案:使用行级锁锁定读取的记录

将查询手机号的语句改为SELECT ... FOR UPDATE,这样在读取记录时会对该行加排他锁,其他事务必须等待当前事务提交/回滚释放锁后才能读取,从根本上避免重复读取。

同时可以优化代码逻辑,去掉多余的count查询,合并为一次查询,减少数据库交互:

try {
    $pdo->beginTransaction();

    // 用SELECT ... FOR UPDATE锁定该行,避免并发读取
    $get_number = $pdo->prepare("SELECT * FROM numbers WHERE user = :user ORDER BY id DESC LIMIT 1 FOR UPDATE");
    $get_number->bindParam(':user', $user);
    
    if (!$get_number->execute()) {
        throw new Exception('Failed to fetch number');
    }

    $data = $get_number->fetch(PDO::FETCH_ASSOC);
    if ($data) {
        $id = $data['id'];
        $number = trim($data['number']);
        
        $delete = $pdo->prepare("DELETE FROM numbers WHERE id = :id");
        $delete->bindParam(':id', $id);
        
        if (!$delete->execute()) {
            throw new Exception('Failed to delete number');
        }

        $pdo->commit();
        echo json_encode(["success" => true, "number" => $number]);
    } else {
        $pdo->commit(); // 无记录时也提交事务,避免占用连接
        echo json_encode(["success" => false, "number" => "", "error" => "DB empty"]);
    }
    
} catch (Exception $e) {
    $pdo->rollBack();
    echo json_encode(["success" => false, "error" => $e->getMessage()]);
}

关键说明:

  1. FOR UPDATE的作用:在InnoDB引擎下,该语句会对查询到的行加排他锁,其他事务无法读取或修改该行,直到当前事务完成(commit/rollBack)。
  2. 确保表引擎为InnoDB:MyISAM不支持事务和行级锁,必须使用InnoDB才能生效。
  3. 错误处理优化:统一用异常抛出处理失败情况,避免嵌套if的混乱,同时确保事务在异常时正确回滚。
  4. 去掉冗余count查询:直接通过fetch判断是否存在记录,减少一次数据库查询,提升性能。

额外注意事项:

  • 如果并发量极高,可能会出现锁等待超时,可以通过设置innodb_lock_wait_timeout参数调整超时时间,或者在代码中捕获锁等待异常进行重试。
  • 确保数据库连接配置中开启了PDO的错误模式(如PDO::ERRMODE_EXCEPTION),这样执行失败时会抛出异常,便于事务回滚。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 06:05:14