侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

高并发场景下的库存超卖问题解决方案

2025-12-12 / 0 评论 / 4 阅读

题目

高并发场景下的库存超卖问题解决方案

信息

  • 类型:问答
  • 难度:⭐⭐⭐

考点

事务隔离级别,悲观锁与乐观锁,死锁处理,性能优化

快速回答

解决高并发库存扣减的核心要点:

  • 使用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缓冲瞬时流量