UPDATE wp_coupon SET used = 1, used_at = NOW()
WHERE id = 7 AND used = 0;
-- affected_rows 必须为 1,否则回滚
SELECT ROW_COUNT();

用户只有一张满 200 减 50 的券,两笔订单都减了 50,券表里 used 字段还是 0。日志里两个请求的 coupon_id 相同,时间戳只差 12 毫秒。这类现象通常不是突然出现的,往前翻半天日志能看到苗头。

追下去是这么回事:代码先 SELECT 查券是否可用,再在业务末尾 UPDATE 标记已用,中间隔着一次支付请求。两个进程都查到了未使用的记录,各自往下走,后写的覆盖先写的,券被消费两次而余额只扣一次。

这次卡住的地方就在「优惠券并发核销」这一环,单独把它拎出来验证,别和其他改动混在同一次发布里。同一份配置存在多处的,先确认改的是被加载的那一份。

处理上按这个顺序:把核销改成带条件的更新,WHERE 后面必须带 used = 0,受影响行数为 1 才允许继续;这条更新放进订单事务,和订单写入一起提交,回滚时券自动恢复;对同一用户的领券记录加唯一索引 user_id 加 coupon_id,从结构上堵住重复持有。

确认没问题再收工,并发脚本跑一百次,成功下单数等于券的数量,券表和订单数对得上。

作者 9823189