做电商的朋友都知道,用户下单后发现库存还能卖,结果付款时提示没货了,这种体验直接拉低店铺评分。反过来,明明没货了商品还挂在前台可买,更是一堆售后麻烦。这些问题背后,往往不是业务逻辑错了,而是库存同步机制在服务器层面没处理好。
为什么库存数据总对不上
常见的场景是:促销活动一开,几百人同时抢购一件商品。数据库里的库存字段原本是 100,请求一并发过来,服务器没加锁,直接读取、减一、再写回。结果多个请求同时读到同一个值,都基于 100 减到 99,等于只扣了一次,实际却卖出去几十单。这就是典型的“超卖”。
很多人第一反应是前端限制,比如按钮点一次就禁用。但这根本没用——用户可以刷接口,或者换设备提交。真正的防线在服务端,必须保证每次库存变更都是原子操作。
数据库层怎么防并发冲突
最简单的办法是在 SQL 更新时加上条件判断。比如:
UPDATE products SET stock = stock - 1 WHERE id = 123 AND stock > 0;
这样即使多个请求进来,也只会有一条成功把库存从 1 变成 0,其他的因为不满足 stock > 0 条件而更新失败。应用层根据影响行数判断是否扣减成功,比单纯查再改安全得多。
更进一步,可以用数据库的行级锁。比如在事务中先执行:
SELECT stock FROM products WHERE id = 123 FOR UPDATE;
这会锁住这一行,直到事务结束,其他请求只能排队等着。虽然性能稍慢,但在秒杀类场景下很管用。
缓存和数据库要保持一致
很多系统为了快,把库存放在 Redis 里。用户下单先减缓存,再异步写数据库。但一旦中间出错,比如减了缓存服务崩了,数据库没更新,数据就永久不一致了。
建议的做法是“缓存双删”+ 延迟补偿。流程像这样:
- 请求来,先删 Redis 缓存
- 再减数据库库存
- 减成功后再删一次缓存(防止中间有旧值被重新加载)
- 最后通过定时任务扫描数据库和缓存差异,自动修复
这套机制能覆盖大部分异常情况,尤其是网络抖动或服务重启的场景。
消息队列别当成万能解药
有人觉得,把库存变更丢进 Kafka 或 RabbitMQ 异步处理,就能抗住高并发。但异步意味着延迟,用户下单成功,页面显示还有 5 件,实际上消息还在排队,这时候再来一波请求,很可能又卖超了。
如果非要用消息队列,得配合“预占库存”机制。用户下单时先同步扣一个“待支付库存”,进入队列后由消费者真正扣正式库存。订单取消或超时,再把预占还回去。这样既能削峰,又能保准确。
电商库存看着简单,其实牵扯到数据库、缓存、队列、网络多个环节。任何一个点松动,整个系统就可能漏单、超卖、对账对不上。日常维护时多看看日志里的库存变更流水,定期跑一致性校验脚本,比出事后再补救强得多。