标准答案

  1. 例如两个请求同时读取库存为 1,都通过“库存大于 0”的判断,随后各自写入扣减结果,造成超卖。
  2. 复现时固定初始数据,增加并发屏障或延迟,记录请求、线程、版本和 SQL,放大读取与写入之间的窗口。
  3. 修复可以使用数据库条件更新、唯一约束、事务锁、乐观版本号或按业务 Key 串行化。
  4. 验证要检查最终库存、成功订单数、重复副作用和失败请求的可解释结果。

题目解析

竞态的关键不是请求偶尔变慢,而是两个操作都基于同一个旧事实做出决定。只要读取、判断和写入之间存在可交错窗口,低概率也足以造成超卖、重复扣款或状态回退。

复现要控制初始数据和并发时序,例如让多个请求在判断后同时暂停,再放开写入。日志应包含业务键、请求 ID、版本、事务和数据库影响行数,才能把时序还原出来。

修复要把不变量放到原子边界:条件 UPDATE、唯一约束、事务锁、版本校验或按 Key 串行化。仅仅在应用层加一次 if,无法阻止另一个实例同时通过判断。

常见误区

  • 误区:只在单线程或低并发测试。改正:用并发屏障和受控延迟放大读取与写入窗口,并校验最终数据。
  • 误区:用应用层 if 判断代替数据库原子条件。改正:把判断和写入合并为条件更新、约束或事务同步操作。
  • 误区:修复后只看接口返回。改正:同时核对库存、订单、幂等记录和外部副作用,确认没有静默重复或丢失。

作者信息