题目
高并发场景下的库存超卖问题解决方案
信息
- 类型:问答
- 难度:⭐⭐⭐
考点
事务隔离级别,悲观锁与乐观锁,死锁处理,性能优化
快速回答
解决高并发库存扣减的核心要点:
- 使用SELECT FOR UPDATE NOWAIT实现行级悲观锁,避免脏读
- 设置READ COMMITTED隔离级别平衡一致性和并发性
- 订单创建与库存更新在同一个事务中完成
- 死锁检测机制:捕获ORA-00060错误并重试
- 批量提交优化:每100笔订单提交一次事务
问题场景
电商秒杀场景中,1000并发请求同时扣减同一商品库存(初始库存=500),需解决:
1. 超卖问题(库存不能为负)
2. 高并发性能瓶颈
3. 死锁风险
核心解决方案
1. 数据库设计
CREATE TABLE inventory (
product_id NUMBER PRIMARY KEY,
stock NUMBER NOT NULL CHECK(stock >= 0),
version NUMBER DEFAULT 0 -- 乐观锁版本号
);2. 悲观锁实现(推荐方案)
-- 应用层伪代码
BEGIN
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT stock
FROM inventory
WHERE product_id = 123
FOR UPDATE NOWAIT; -- 非阻塞锁
IF stock >= order_quantity THEN
UPDATE inventory
SET stock = stock - order_quantity
WHERE product_id = 123;
INSERT INTO orders(...); -- 创建订单记录
END IF;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -60 THEN -- ORA-00060: 死锁
ROLLBACK;
WAIT 0.1 SECONDS; -- 指数退避重试
RETRY(MAX 3 TIMES);
END IF;
END;关键机制:
FOR UPDATE NOWAIT:立即返回锁冲突错误而非等待- 行级锁:Oracle默认在ROWID级别加锁
- CHECK约束:数据库层防止负数库存
3. 乐观锁替代方案
UPDATE inventory
SET stock = stock - :qty,
version = version + 1
WHERE product_id = 123
AND version = :old_version -- 携带查询时的版本号
AND stock >= :qty;优劣对比:
| 方案 | 适用场景 | TPS | 缺点 |
|---|---|---|---|
| 悲观锁 | 冲突率高 | 3000+ | 可能死锁 |
| 乐观锁 | 冲突率低 | 5000+ | 重试逻辑复杂 |
性能优化技巧
- 批量提交:每100笔订单提交一次事务
- 索引优化:确保product_id有唯一索引
- 连接池配置:增大INITIAL_SIZE减少连接创建开销
- 热点分离:库存数据与订单数据分表存储
常见错误
- 未处理死锁:未捕获ORA-00060导致事务挂起
- 锁范围过大:误用LOCK TABLE导致全表锁定
- ABA问题:乐观锁未结合版本号校验
- 事务过长:包含非必要操作增大锁持有时间
扩展知识
- Oracle特性:
-SKIP LOCKED(12c+)跳过锁定的行
-WAIT n指定锁等待超时时间 - 架构升级:
- 分库分表:按商品ID哈希分片
- 缓存层:Redis预扣库存+异步落库
- 队列削峰:Kafka缓冲瞬时流量