侧边栏壁纸
博主头像
colo

欲买桂花同载酒

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

高并发场景下如何设计库存扣减系统避免超卖

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

题目

高并发场景下如何设计库存扣减系统避免超卖

信息

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

考点

事务隔离级别,锁机制,并发控制,数据库设计,分布式事务

快速回答

核心解决方案要点:

  • 事务隔离级别:使用REPEATABLE READSERIALIZABLE保证数据一致性
  • 锁机制选择:悲观锁(SELECT FOR UPDATE)或乐观锁(版本号控制)
  • 数据库设计:库存字段使用无符号整数,添加CHECK约束防止负数
  • 分层防御:应用层限流 + 缓存预扣减 + 数据库最终扣减
  • 补偿机制:通过日志和定时任务处理异常状态
## 解析

问题背景

在高并发秒杀场景中,当多个请求同时扣减同一商品库存时,可能出现超卖(库存减为负数)。例如:

-- 错误示例(非原子操作)
SELECT stock FROM products WHERE id=100; -- 假设查到stock=1
UPDATE products SET stock=stock-1 WHERE id=100; -- 并发时可能被多个线程执行

核心解决方案

1. 数据库层方案

方案A:悲观锁(行级锁)

BEGIN;
-- 锁定目标行(MySQL InnoDB)
SELECT stock FROM products WHERE id=100 FOR UPDATE;

-- 应用层校验
IF stock > 0 THEN
  UPDATE products SET stock=stock-1 WHERE id=100;
END IF;
COMMIT;
  • 优点:强一致性
  • 缺点:并发性能低,可能死锁
  • 隔离级别:需REPEATABLE READ及以上

方案B:乐观锁(版本控制)

UPDATE products 
SET stock=stock-1, version=version+1 
WHERE id=100 
AND stock>0 
AND version=#{current_version} -- 应用层传递当前版本号
  • 优点:高并发性能好
  • 缺点:需处理大量失败请求
  • 注意:需配合重试机制

方案C:直接条件更新

UPDATE products SET stock=stock-1 
WHERE id=100 AND stock>0; -- 数据库原子操作
  • 优点:简单高效,无需事务
  • 缺点:无法获取扣减后库存值

2. 架构层增强方案

分层防御体系:

  1. 接入层:Nginx限流(令牌桶算法)
  2. 服务层:Redis分布式锁 + Lua脚本预扣减
    -- Redis原子操作示例
    if redis.call('get', 'stock_100') > '0' then
      return redis.call('decr', 'stock_100')
    else
      return 0
    end
  3. 数据库层:最终一致性扣减(通过MQ异步执行)

最佳实践

  • 库存分段:将库存拆分为多桶(如10桶×100件),分散锁竞争
  • 热点数据处理
    • Redis集群分片存储热点商品
    • 本地缓存+标记位快速过滤无效请求
  • 事务优化
    • 事务范围最小化
    • 避免跨服务分布式事务(用最终一致性替代)

常见错误

  • 错误1:在应用层计算库存(未用数据库原子操作)
  • 错误2READ COMMITTED隔离级别下二次查询导致幻读
  • 错误3:Redis预扣减后未设超时时间,导致库存冻结

扩展知识

  • 分布式事务方案
    • TCC(Try-Confirm-Cancel)
    • 基于MQ的最终一致性
  • 数据库选型
    • TiDB(分布式HTAP数据库)
    • 阿里云PolarDB(读写分离架构)
  • 容灾设计
    • Redis与DB双写一致性方案(先更DB再删缓存)
    • 库存扣减流水表(用于对账补偿)