题目
高并发场景下如何实现订单库存的精准扣减与超卖防护
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
事务隔离级别,悲观锁与乐观锁,高并发优化,死锁处理
快速回答
在高并发订单系统中实现精准库存扣减需综合运用:
- 事务隔离级别:使用 REPEATABLE READ 或 SERIALIZABLE
- 锁机制选择:
- 悲观锁:SELECT ... FOR UPDATE
- 乐观锁:版本号/时间戳校验
- 防超卖核心:WHERE stock >= quantity 原子操作
- 性能优化:队列削峰、缓存库存、热点数据分离
问题场景
电商秒杀场景中,1000件商品同时接收5000个请求,需保证:
1. 库存扣减不出现超卖
2. 高并发下系统不崩溃
3. 扣减操作具备原子性
核心解决方案
方案1:悲观锁实现
START TRANSACTION;
SELECT stock FROM products WHERE id=1001 FOR UPDATE; -- 获取排他锁
-- 应用层校验库存
UPDATE products
SET stock = stock - 1
WHERE id = 1001 AND stock >= 1; -- 关键原子操作
COMMIT;注意事项:
- 必须使用事务且隔离级别≥REPEATABLE READ
- FOR UPDATE 会导致行锁竞争,可能引发死锁
- 超时处理:设置 innodb_lock_wait_timeout
方案2:乐观锁实现
UPDATE products
SET stock = stock - 1,
version = version + 1
WHERE id = 1001
AND stock >= 1
AND version = #{current_version}; -- 版本号校验执行结果处理:
- 影响行数=1:扣减成功
- 影响行数=0:触发重试或返回失败
性能优化策略
| 策略 | 实现方式 | 适用场景 |
|---|---|---|
| 队列削峰 | Redis List/RabbitMQ 缓冲请求 | 秒杀场景 |
| 缓存库存 | Redis 预减库存 + 异步落库 | 读多写少 |
| 热点分离 | 库存字段拆分到独立表 | 高频更新商品 |
常见错误与规避
- 超卖问题:
错误做法:先查询后更新(非原子操作)
正确做法:UPDATE 语句中内置库存校验 - 死锁陷阱:
错误:事务内多行更新顺序不一致
规避:统一按主键排序更新 - 性能瓶颈:
错误:所有商品共用库存表
优化:按商品ID分库分表
扩展知识
- 隔离级别对比:
READ COMMITTED 存在不可重复读风险
SERIALIZABLE 性能损耗需权衡 - 分布式锁方案:
Redis Redlock 或 ZooKeeper 实现跨服务锁 - 最终一致性:
MQ事务消息 + 库存流水表补偿机制
最佳实践总结
- 优先使用乐观锁(版本号)减少锁竞争
- UPDATE 语句必须包含 stock >= quantity 条件
- 事务范围最小化(避免长事务)
- 热点商品采用库存分段:将1000库存拆为10个100库存的分桶
- 监控死锁日志:SHOW ENGINE INNODB STATUS