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()]); }
关键说明:
FOR UPDATE的作用:在InnoDB引擎下,该语句会对查询到的行加排他锁,其他事务无法读取或修改该行,直到当前事务完成(commit/rollBack)。- 确保表引擎为InnoDB:MyISAM不支持事务和行级锁,必须使用InnoDB才能生效。
- 错误处理优化:统一用异常抛出处理失败情况,避免嵌套if的混乱,同时确保事务在异常时正确回滚。
- 去掉冗余count查询:直接通过
fetch判断是否存在记录,减少一次数据库查询,提升性能。
额外注意事项:
- 如果并发量极高,可能会出现锁等待超时,可以通过设置
innodb_lock_wait_timeout参数调整超时时间,或者在代码中捕获锁等待异常进行重试。 - 确保数据库连接配置中开启了PDO的错误模式(如
PDO::ERRMODE_EXCEPTION),这样执行失败时会抛出异常,便于事务回滚。
内容的提问来源于stack exchange,提问作者Jose Florez
相关产品推荐
相关产品推荐

