店里两台收银机、一台平板同时开单,同一款商品明明只剩3件,却卖出去5件。2026年了,这种事还在天天发生。别急着怪系统,先把问题拆开看。
一、先分清是超卖还是重复开单
两个毛病长得像,根子完全不一样。
- 超卖:库存显示还有货,实际早卖光了,属于并发扣减没做对。
- 重复开单:同一个顾客被扣两次钱、生成两张单,属于请求没去重。
排查方法很土但管用:翻后台的库存流水,看同一个SKU在几秒内是不是冒出两条扣减记录;再看订单表里有没有手机号、金额、时间几乎一样的两条单。有,就是重复开单;没有,就是超卖。
二、库存锁定:先锁后卖,别先卖后扣
很多系统是下单成功了才扣库存,中间那几秒就是事故窗口。
正确做法是加一层库存锁定:顾客点结算的瞬间,先把这1件锁住,锁定有效期给到15分钟。付款成功转成实扣,超时或取消自动释放。
有个做生鲜的商家,2026年3月把锁定从“无”改成“15分钟”,周末高峰的超卖投诉从每天20多单降到2单。
三、并发扣减:别让两台机器读同一个数
收银A读到库存100,收银B也读到100,两边各卖1件,写回去都是99,凭空丢掉一单。
解决办法一句话:扣减必须原子。用一条带条件的更新语句,库存 = 库存 - 1 where 库存 >= 1,然后看影响行数是不是1。是0就说明没抢到,直接提示售罄。千万别写“先查库存再改库存”的两步代码,那是在给自己埋雷。
四、订单去重:给每笔单发一张身份证
网络卡一下,收银员手一抖点了两次提交,两张单就出来了。
前端生成一个唯一请求号,跟着订单一起传。服务端拿这个号建唯一索引,第二次进来直接返回第一次的结果,不再建单。再加一条软性拦截:同一会员、同一商品、30秒内重复提交,弹窗让收银员确认。
五、今天就能动手的三件事
- 查一遍库存扣减的SQL,凡是不带where条件的,全部改掉。
- 给订单表加请求号唯一索引,历史脏数据先不动。
- 拿两台设备同时下一单实测,看库存数和订单数对不对得上。
别等出事了再查日志。这两个改动加起来不到一天工时,能省掉后面一堆退款和客诉。