首页/安全/漏洞与攻击面速查/并发竞争条件(Race Condition)
Web 漏洞原理与防护55 条

并发竞争条件(Race Condition)

并发请求在『检查-使用』间隙同时通过校验,可造成重复领券、超额提现、绕过限额;防护是把检查与扣减合并为原子操作(数据库约束、条件更新、行锁或队列串行化)。

漏洞与攻击面速查安全更新于 2026-10-13

竞争条件怎么出现

竞争条件指并发请求在「检查—使用」的间隙同时通过校验,典型表现是重复领券、超额提现、库存超卖、绕过次数限制。根因是校验与写操作不是原子的:两个请求都读到了「还有余额」的旧状态,于是都执行了扣减。防护方向有三个:把检查与扣减合并成一条原子操作、用唯一约束兜住重复、或者把并发请求串行化。

基本信息

典型场景领券与秒杀、提现与转账、库存扣减、短信次数限制、试用与注册奖励
根因检查与写入分离、缺少唯一约束、缓存与数据库状态不一致
防护手段条件更新、唯一索引、行锁、队列串行化、幂等键
验证方式并发请求工具(ab、wrk 或自定义脚本),并发必须真实同时到达
回归重点冲突异常处理与用户提示,避免漏洞修完变成 500
关联设计接口幂等性与重试策略

手段与适用条件

手段适用条件注意点
条件更新单库单行的余额与计数以影响行数判断结果,不依赖前置查询
唯一索引同一用户同一活动仅允许一次必须捕获重复键异常并转成业务提示
SELECT FOR UPDATE 行锁需要两步读取与写入控制锁范围与事务时长,避免死锁
队列串行化跨服务或跨库场景引入排队延迟,需幂等键去重
分布式锁多实例并发写同一资源超时与释放策略要明确,避免锁泄漏

条件更新(原子且幂等)

条件更新(原子且幂等)
-- 风险:先查余额再扣减,两步之间可能被并发穿插
-- SELECT balance FROM account WHERE id = 1;
-- UPDATE account SET balance = balance - 100 WHERE id = 1;

-- 修复:条件更新一条语句完成,原子且幂等
UPDATE account SET balance = balance - 100
WHERE id = 1 AND balance >= 100;
-- 影响行数为 0 表示余额不足,直接拒绝本次请求,不再二次查询

其他手段

其他手段
唯一索引防重复记录:同一用户同一活动仅一条,重复插入报冲突
SELECT ... FOR UPDATE 行锁:注意锁范围与死锁风险,事务尽量短
分布式场景用队列或幂等服务串行化处理,配幂等键去重
业务层兜底:重复请求返回「已处理」而不是报错,减少用户重复提交

典型使用场景

  • 营销活动与秒杀上线前的并发评审
  • 提现、支付链路的对账与差异排查
  • 库存与限额类接口的加固
  • 渗透测试报告中竞争条件类问题的整改
  • 用并发工具复现与验证修复效果
  • 放量前的压测与幂等性回归
复现并发问题需要并发请求工具(ab、wrk 或自定义脚本),手工点击无法暴露。验证时注意请求要真正同时到达,否则会误判为已修复。
常见的修复组合是「条件更新 + 唯一索引 + 幂等键」三层。跨服务场景下只做其中一层,仍可能在网络重试与补偿环节漏掉。

易错点

  • 加了唯一约束但不处理冲突异常:并发下后续请求直接 500,问题从漏洞变成稳定性事故
  • 用前置查询判断再写入,两步之间仍是竞态窗口,看着加了校验其实没解决
  • 只在缓存里做计数而数据库没有约束,缓存失效或双写不一致时超额
  • 行锁范围过大或事务里调用外部接口,锁等待把连接池拖垮
  • 只在测试环境用串行请求验证,并发路径根本没被覆盖

数据来源与参考

  • OWASP 竞争条件相关说明
  • MySQL 8.0 Reference Manual(InnoDB 锁)
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌