两个请求几乎同时下单,第二个请求拿到锁时读到的库存还是旧值,扣完卖出负库存。日志里锁的释放时间比事务提交早了 12 毫秒。日志里能对上时间点,这种反而好排。

锁加在业务方法里,事务提交发生在方法返回之后。锁先释放事务还没提交,第二个请求读不到第一个请求的更新,按旧值又扣了一次。这一点先确认清楚,后面就不用反复试。

UPDATE stock SET qty = qty - 1
WHERE goods_id = 8812 AND qty >= 1;
# 影响行数为 0 说明库存不足 直接返回失败

「锁与事务边界」确认了原因之后,改动尽量只碰那一处,顺手改的东西后面最容易出问题。线上出问题先对时间点,跟发布、备份、任务执行的时刻排一排,能省掉一半功夫。

处理上按这个顺序:把锁包在事务外层 用编程式事务控制提交时机;确认提交完成再释放锁 释放放在 finally 最后;锁的 value 带上请求标识 只删自己那把;库存更新补 where qty >= n 兜底。

结果对不对很好判断,并发压测下库存不会出现负数 两个请求串行执行 后一个读到的是前一个提交后的值。

作者 9823189