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

利用RDBMS事务避免竞态条件:分布式系统计数一致性咨询

问题分析与解决方案

一、当前场景是否会产生数据不一致?

会产生数据不一致,最终大概率得到id=2的val为1,而非预期的2。

原因在于当前的「读-改-写」逻辑,即使包裹在事务中,默认的数据库事务隔离级别(如Read Committed)无法阻止并发更新丢失:

  • 实例1处理Event-1,10:00:00启动事务,读取id=2无记录,count设为0,加1后变为1,未提交;
  • 实例2处理Event-2,10:00:00:50启动事务,同样读取id=2无记录,count设为0,加1后变为1;
  • 实例2耗时2秒,先在10:00:02:50提交事务,将id=2的val更新为1;
  • 实例1在10:00:03提交事务,再次将id=2的val更新为1,覆盖实例2的结果,最终val为1而非2。

事务仅保证单个操作的原子性,但无法解决跨事务的「读-改-写」竞态问题。

二、除事务外的竞态条件处理方案

1. 数据库悲观锁(行级锁)

读取数据时显式加行级锁,确保同一时间只有一个事务能修改该行:

START TRANSACTION;
SELECT val FROM table WHERE id = ? FOR UPDATE; -- 加排他锁,其他事务需等待锁释放
-- 无记录则count=0,count+1
INSERT INTO table (id, val) VALUES (?, 1) ON DUPLICATE KEY UPDATE val = val + 1;
COMMIT;

SELECT ... FOR UPDATE会锁定目标行,直到当前事务提交,避免并发读取旧值。

2. 数据库乐观锁

通过版本号校验实现,不主动加锁,更新时验证数据是否被修改:

  1. 给表新增version字段,初始值为0;
  2. 更新逻辑:
-- 先读取当前值和版本号
SELECT val, version FROM table WHERE id = ?;
-- 仅当版本号未变化时执行更新
UPDATE table SET val = ?, version = version + 1 WHERE id = ? AND version = ?;

若更新影响行数为0,说明存在并发修改,需重试操作。

3. 原子化SQL操作

将「读-改-写」合并为单个原子SQL语句,利用数据库原子性保证操作唯一:

  • MySQL:
INSERT INTO table (id, val) VALUES (?, 1) ON DUPLICATE KEY UPDATE val = val + 1;
  • PostgreSQL:
INSERT INTO table (id, val) VALUES (?, 1) ON CONFLICT (id) DO UPDATE SET val = table.val + 1;

无需额外事务包裹,数据库直接保证操作原子性,从根源避免竞态。

4. 分布式锁

跨节点部署场景下,用分布式锁(如Redis锁)控制同一id的并发处理:

  • 处理前尝试获取锁:SET lock:id:2 "locked" NX EX 5(Redis命令,NX表示仅锁不存在时设置,EX设超时时间);
  • 成功获取锁后执行更新,完成后释放锁;
  • 未获取锁则等待或重试,确保同一时间仅一个实例处理该id事件。
    需注意处理锁超时、续约问题,避免死锁或锁失效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 19:23:11