高并发请求下MySQL查询与更新失效问题求助
解决数据库余额更新的并发失效问题
这是典型的竞态条件(Race Condition):高并发场景下,多个请求同时读取到相同的balance值,各自完成加10计算后再执行更新,导致后续请求的更新覆盖了前面的结果,最终出现重复值。
1. 使用数据库原子更新语句(最优方案)
直接在SQL层面完成「读取-计算-更新」的原子操作,彻底避免先读再写的分离步骤,这是最可靠且性能最高的方式。
错误写法(先读再写,存在竞态):
$stmt = $pdo->query("SELECT balance FROM users WHERE id = 1"); $balance = $stmt->fetchColumn(); $newBalance = $balance + 10; $pdo->query("UPDATE users SET balance = $newBalance WHERE id = 1");
正确写法(原子操作):
$stmt = $pdo->prepare("UPDATE users SET balance = balance + 10 WHERE id = 1"); $stmt->execute();
这条SQL会在数据库引擎层面锁定目标行,全程原子执行,不会被其他请求打断。
2. 使用行级悲观锁(适合复杂业务逻辑)
如果必须先读取数据做额外业务处理再更新,可以用SELECT ... FOR UPDATE给行加锁,确保同一时间只有一个请求能操作该行。
// 开启事务 $pdo->beginTransaction(); try { // 锁定目标行,其他请求需等待当前事务完成才能读取 $stmt = $pdo->prepare("SELECT balance FROM users WHERE id = 1 FOR UPDATE"); $stmt->execute(); $balance = $stmt->fetchColumn(); // 这里可加入其他业务逻辑 $newBalance = $balance + 10; $stmt = $pdo->prepare("UPDATE users SET balance = ? WHERE id = 1"); $stmt->execute([$newBalance]); $pdo->commit(); } catch (Exception $e) { $pdo->rollBack(); throw $e; }
注意:需确保数据库引擎支持行级锁(如InnoDB),MyISAM仅支持表锁,会严重影响性能。
3. 使用乐观锁(适合中低并发场景)
假设并发冲突较少,通过版本号控制更新,冲突时自动重试。
首先给表添加版本字段:
ALTER TABLE users ADD COLUMN version INT DEFAULT 1;
更新逻辑:
do { // 读取当前余额和版本号 $stmt = $pdo->prepare("SELECT balance, version FROM users WHERE id = 1"); $stmt->execute(); $data = $stmt->fetch(PDO::FETCH_ASSOC); $currentBalance = $data['balance']; $currentVersion = $data['version']; $newBalance = $currentBalance + 10; $newVersion = $currentVersion + 1; // 仅当版本号匹配时才更新,不匹配则重试 $stmt = $pdo->prepare("UPDATE users SET balance = ?, version = ? WHERE id = 1 AND version = ?"); $stmt->execute([$newBalance, $newVersion, $currentVersion]); } while ($pdo->rowCount() === 0);
额外提醒
- 不要依赖前端防抖/节流控制请求,必须在后端做并发处理,前端限制可被绕过。
- 修复后建议用压测工具(如ab、JMeter)模拟高并发场景,验证效果。
内容的提问来源于stack exchange,提问作者Sukrat
相关产品推荐
相关产品推荐

