UPDATE quota SET used = used + 1 WHERE user_id = 3312 AND used < total; -- 影响行数 0 表示额度已用完
压测 50 并发扣同一个账号的配额,初始剩 1,结束后 used 变成 -3。日志里三个线程打印的剩余额度都是 1,最终写回的值分别是 0、-1、-2。这类现象通常不是突然出现的,往前翻半天日志能看到苗头。
追下去是这么回事:先 select 出剩余量再在应用层减一,扣减和写回之间有窗口。数据库没有加行锁也没有版本号,多个事务读到同一个旧值,各自算出结果覆盖写回。
「配额超卖」确认了原因之后,改动尽量只碰那一处,顺手改的东西后面最容易出问题。同一份配置存在多处的,先确认改的是被加载的那一份。
新版本先放一台机器上跑一天再全量,问题一般在这段时间暴露。
处理上按这个顺序:扣减改成一条带条件的 update 让数据库判断余量;影响行数为 0 就返回额度不足 不要重试覆盖;余额查询和扣减分开 查询走缓存 扣减走主库。
确认没问题再收工,50 并发压测后 used 恰好等于 total,没有出现负数,失败请求返回 429 额度不足。
