核心是用mysql的update...where stock>0+影响行数校验实现原子扣减,再以redis预扣减分层过滤流量,事务内仅保留扣库存和生成订单,配合兜底监控确保分钟级可定位修复。

用数据库行锁 + 乐观锁兜底,避免超卖
秒杀场景下库存扣减的核心矛盾是:多个请求几乎同时读到“还有10件”,然后都去减库存,结果变成-5。单纯靠应用层加 synchronized 或 Redis 分布式锁,容易成为性能瓶颈或单点故障。真正可靠的做法是让数据库承担最终一致性责任。
推荐组合:MySQL 的 UPDATE ... WHERE stock > 0 + 影响行数校验。例如:
UPDATE item_stock SET stock = stock - 1 WHERE item_id = ? AND stock > 0;执行后检查 JDBC 的 executeUpdate() 返回值:等于 1 表示扣减成功;等于 0 表示库存不足或已被抢光。这利用了 InnoDB 的当前读(SELECT ... FOR UPDATE 也可,但开销更大),天然具备行级排他性,且无需额外锁服务。
预减库存 + 异步落库,把压力挡在数据库前
纯 DB 扣减在百万 QPS 下仍可能打满连接池或引发锁等待。更优解是分层过滤:
- 第一层:Redis 原子操作预扣(
DECR stock:1001),返回负数则直接拦截; - 第二层:DB 执行带条件的 UPDATE,失败则回滚 Redis(用 Lua 保证原子性);
- 第三层:扣减成功后发 MQ 消息,由消费者异步更新销量、记录订单等重逻辑。
这样 DB 只承担最终校验,90%+ 请求在 Redis 层就被拒绝,真正落到 MySQL 的是已通过预校验的“优质流量”。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
避免事务过长,所有非核心操作移出事务边界
常见错误是在一个 @Transactional 方法里查用户、发短信、写日志、调积分服务……再加库存扣减,导致事务持续几百毫秒,锁住库存行太久。正确做法是:
- 事务内只做两件事:扣库存(UPDATE)和生成订单(INSERT);
- 用户校验、风控、通知、积分变动等全部异步化或事后补偿;
- 必要时对订单表分库分表,避免单表写入成为瓶颈。
库存行锁持有时间控制在 10ms 内,才能扛住每秒数万真实扣减请求。
超卖兜底与监控必须到位
再严谨的设计也难保 100% 零超卖,关键是要快速发现、可追溯、能补偿:
- 每日定时任务比对 Redis 库存快照与 DB 实际剩余,告警偏差;
- 每笔扣减记录完整上下文(traceId、itemId、userId、时间戳、前后库存值);
- 设计“库存回滚接口”,支持人工或自动触发异常订单的库存返还。
不追求绝对完美,而要确保问题在分钟级可定位、小时级可修复。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










