2026/4/8 13:11:27
网站建设
项目流程
unity3d转行网站开发,网站开发售后服务能力,上海十大好厂,泉州建设银行网站标题#xff1a; 超卖#xff1f;不存在的#xff01;五大方案让你高枕无忧
副标题#xff1a; 从数据库锁到Redis原子操作#xff0c;防超卖全攻略#x1f3ac; 开篇#xff1a;一个惨痛的案例makefile体验AI代码助手代码解读复制代码双11零点#xff0c;某电商平台超卖不存在的五大方案让你高枕无忧副标题从数据库锁到Redis原子操作防超卖全攻略 开篇一个惨痛的案例makefile体验AI代码助手代码解读复制代码双11零点某电商平台 00:00:00 - 秒杀活动开始 商品库存100件 00:00:05 - 系统显示已售罄 数据库库存-23件 实际订单123个 00:01:00 - 客服电话被打爆 客户A我买到了为什么取消我订单 客户B明明还有库存为什么买不了 损失 - 赔偿金10万 - 用户流失1000 - 品牌损失无法估量 CEO这个锅谁来背 超卖问题的本质想象10个人同时抢1件商品没有库存控制10个人都看到有货都下单成功超卖9件有库存控制只有1个人抢到其他9个人提示已售罄完美核心问题并发场景下的库存扣减不是原子操作 知识地图体验AI代码助手代码解读复制代码防止超卖的五大方案 ├── 方案1悲观锁数据库锁 ├── 方案2乐观锁版本号 ├── ⚡ 方案3Redis原子操作推荐 ├── 方案4消息队列削峰 └── 方案5分布式锁预扣库存 方案1悲观锁 - 数据库行锁 生活中的例子银行取款机没有锁两个人同时取钱余额可能出错有锁第一个人操作时第二个人只能等待 技术实现实现1SELECT ... FOR UPDATEjava体验AI代码助手代码解读复制代码/** * 悲观锁方案使用SELECT FOR UPDATE */ Service public class PessimisticLockStockService { Autowired private ProductMapper productMapper; Autowired private OrderService orderService; /** * 购买商品悲观锁 */ Transactional(rollbackFor Exception.class) public boolean buyProduct(Long productId, Integer quantity) { // 1. 查询并锁定商品行锁 Product product productMapper.selectForUpdate(productId); // 2. 检查库存 if (product.getStock() quantity) { log.warn(库存不足商品ID{}, 剩余库存{}, 购买数量{}, productId, product.getStock(), quantity); return false; } // 3. 扣减库存 int updated productMapper.updateStock(productId, quantity); if (updated 0) { log.error(扣减库存失败商品ID{}, productId); return false; } // 4. 创建订单 orderService.createOrder(productId, quantity); log.info(购买成功商品ID{}, 数量{}, productId, quantity); return true; } } /** * MyBatis Mapper */ Mapper public interface ProductMapper { /** * 查询并锁定关键 */ Select(SELECT * FROM product WHERE id #{id} FOR UPDATE) Product selectForUpdate(Param(id) Long id); /** * 扣减库存 */ Update(UPDATE product SET stock stock - #{quantity} WHERE id #{id}) int updateStock(Param(id) Long id, Param(quantity) Integer quantity); } /** * 原理说明 * * SELECT ... FOR UPDATE 会对查询到的行加排他锁X锁 * * 事务ASELECT ... FOR UPDATE ✅ 获取锁 * 事务BSELECT ... FOR UPDATE ⏳ 等待... * 事务CSELECT ... FOR UPDATE ⏳ 等待... * * 事务A扣减库存 - COMMIT 释放锁 * 事务B ✅ 获取锁 * 事务C ⏳ 等待... * * 优点 * ✅ 强一致性绝不超卖 * ✅ 实现简单 * * 缺点 * ❌ 性能差串行执行 * ❌ 容易死锁 * ❌ 不适合高并发场景 */实现2UPDATE直接加锁java体验AI代码助手代码解读复制代码/** * 更简洁的悲观锁方案 */ Service public class SimplePessimisticLockService { Autowired private ProductMapper productMapper; Transactional(rollbackFor Exception.class) public boolean buyProduct(Long productId, Integer quantity) { // ⚡ 直接扣减库存MySQL会自动加行锁 int updated productMapper.decreaseStock(productId, quantity); if (updated 0) { log.warn(扣减库存失败可能库存不足); return false; } // 创建订单 orderService.createOrder(productId, quantity); return true; } } Mapper public interface ProductMapper { /** * 扣减库存带库存检查 */ Update(UPDATE product SET stock stock - #{quantity} WHERE id #{id} AND stock #{quantity}) // ⚡ 关键检查库存是否足够 int decreaseStock(Param(id) Long id, Param(quantity) Integer quantity); } /** * SQL执行流程 * * UPDATE product * SET stock stock - 10 * WHERE id 1 * AND stock 10 -- 如果不满足updated 0 * * 这个SQL会 * 1. 自动对id1的行加X锁 * 2. 检查库存是否10 * 3. 如果满足扣减库存否则不更新 * 4. 返回影响行数0或1 * * 完美解决超卖问题✅ */性能测试java体验AI代码助手代码解读复制代码/** * 悲观锁性能测试 */ public class PessimisticLockTest { public static void main(String[] args) throws Exception { // 初始库存100件 // 并发数100个线程 // 每个线程购买1件 int concurrency 100; CountDownLatch latch new CountDownLatch(concurrency); for (int i 0; i concurrency; i) { new Thread(() - { try { stockService.buyProduct(1L, 1); } finally { latch.countDown(); } }).start(); } latch.await(); /** * 测试结果 * - 最终库存0件 ✅ 正确 * - 成功订单100个 ✅ 正确 * - 平均耗时1500ms 太慢了 * - TPS67 低 */ } } 方案2乐观锁 - 版本号机制 生活中的例子编辑Word文档悲观锁我编辑时锁定文档你不能看乐观锁我们都可以编辑提交时检查版本冲突了就重试 技术实现sql体验AI代码助手代码解读复制代码-- 数据库表结构 CREATE TABLE product ( id BIGINT PRIMARY KEY, name VARCHAR(100), stock INT, version INT DEFAULT 0, -- ⚡ 版本号字段关键 INDEX idx_version (version) );java体验AI代码助手代码解读复制代码/** * 乐观锁方案使用版本号 */ Service public class OptimisticLockStockService { Autowired private ProductMapper productMapper; /** * 购买商品乐观锁 重试 */ public boolean buyProduct(Long productId, Integer quantity) { int maxRetry 3; // 最多重试3次 for (int i 0; i maxRetry; i) { try { // 1. 查询商品不加锁 Product product productMapper.selectById(productId); // 2. 检查库存 if (product.getStock() quantity) { log.warn(库存不足); return false; } // 3. ⚡ 乐观锁扣减库存基于版本号 int updated productMapper.decreaseStockWithVersion( productId, quantity, product.getVersion() // 传入旧版本号 ); if (updated 0) { // 版本号不匹配说明被其他线程修改了重试 log.warn(版本冲突重试第{}次, i 1); Thread.sleep(10); // 短暂休眠后重试 continue; } // 4. 创建订单 orderService.createOrder(productId, quantity); log.info(购买成功商品ID{}, 数量{}, productId, quantity); return true; } catch (Exception e) { log.error(购买失败, e); if (i maxRetry - 1) { return false; } } } log.error(重试{}次后仍然失败, maxRetry); return false; } } Mapper public interface ProductMapper { /** * 基于版本号扣减库存乐观锁 */ Update(UPDATE product SET stock stock - #{quantity}, version version 1 // 版本号1 WHERE id #{id} AND stock #{quantity} AND version #{version}) // ⚡ 检查版本号关键 int decreaseStockWithVersion(Param(id) Long id, Param(quantity) Integer quantity, Param(version) Integer version); } /** * 原理说明 * * 初始状态stock10, version0 * * 线程A查询stock10, version0 * 线程B查询stock10, version0 * * 线程A更新UPDATE ... WHERE version0 ✅ 成功version变为1 * 线程B更新UPDATE ... WHERE version0 ❌ 失败version已经是1了 * * 线程B重试 * - 重新查询stock9, version1 * - 再次更新UPDATE ... WHERE version1 ✅ 成功 * * 优点 * ✅ 性能比悲观锁好 * ✅ 不会死锁 * ✅ 并发度高 * * 缺点 * ⚠️ 高并发下重试次数多 * ⚠️ 可能导致活锁 * ⚠️ 需要业务层重试逻辑 */改进版ABA问题处理java体验AI代码助手代码解读复制代码/** * 使用时间戳替代版本号防止ABA问题 */ Service public class TimestampOptimisticLockService { Transactional(rollbackFor Exception.class) public boolean buyProduct(Long productId, Integer quantity) { // 1. 查询商品 Product product productMapper.selectById(productId); // 2. 检查库存 if (product.getStock() quantity) { return false; } // 3. ⚡ 使用update_time作为乐观锁 int updated productMapper.decreaseStockWithTimestamp( productId, quantity, product.getUpdateTime() // 传入旧的更新时间 ); if (updated 0) { throw new OptimisticLockException(库存更新冲突); } // 4. 创建订单 orderService.createOrder(productId, quantity); return true; } } Mapper public interface ProductMapper { /** * 基于时间戳的乐观锁 */ Update(UPDATE product SET stock stock - #{quantity}, update_time NOW() // 更新时间戳 WHERE id #{id} AND stock #{quantity} AND update_time #{oldUpdateTime}) // 检查旧时间戳 int decreaseStockWithTimestamp(Param(id) Long id, Param(quantity) Integer quantity, Param(oldUpdateTime) Date oldUpdateTime); }⚡ 方案3Redis原子操作 - 高性能之王推荐 生活中的例子抢红包数据库方案每个人都要去银行排队慢Redis方案微信服务器直接扣减瞬间完成快 技术实现实现1Redis DECR原子操作java体验AI代码助手代码解读复制代码/** * Redis方案使用DECR原子操作 */ Service public class RedisStockService { Autowired private StringRedisTemplate redisTemplate; Autowired private OrderService orderService; /** * 初始化库存到Redis */ public void initStock(Long productId, Integer stock) { String key product:stock: productId; redisTemplate.opsForValue().set(key, String.valueOf(stock)); } /** * 购买商品Redis原子操作 */ public boolean buyProduct(Long productId, Integer quantity) { String stockKey product:stock: productId; // ⚡ Redis原子操作扣减库存 Long remainStock redisTemplate.opsForValue() .decrement(stockKey, quantity); if (remainStock null || remainStock 0) { // 库存不足回滚 if (remainStock ! null remainStock 0) { redisTemplate.opsForValue().increment(stockKey, quantity); } log.warn(库存不足商品ID{}, 剩余库存{}, productId, remainStock); return false; } try { // 异步创建订单不阻塞扣减库存 orderService.createOrderAsync(productId, quantity); log.info(购买成功商品ID{}, 数量{}, 剩余库存{}, productId, quantity, remainStock); return true; } catch (Exception e) { // 订单创建失败回滚库存 redisTemplate.opsForValue().increment(stockKey, quantity); log.error(创建订单失败已回滚库存, e); return false; } } /** * 查询剩余库存 */ public Integer getStock(Long productId) { String stockKey product:stock: productId; String stock redisTemplate.opsForValue().get(stockKey); return stock ! null ? Integer.parseInt(stock) : 0; } } /** * 性能对比 * * 数据库悲观锁TPS 67 * 数据库乐观锁TPS 500 ✅ * Redis原子操作TPS 10000 快150倍 */实现2Lua脚本更强大java体验AI代码助手代码解读复制代码/** * Redis Lua脚本方案原子性更强 */ Service public class RedisLuaStockService { Autowired private StringRedisTemplate redisTemplate; /** * Lua脚本扣减库存 */ private static final String LUA_SCRIPT local stock_key KEYS[1] local quantity tonumber(ARGV[1]) local stock tonumber(redis.call(get, stock_key) or 0) if stock quantity then redis.call(decrby, stock_key, quantity) return stock - quantity // 返回剩余库存 else return -1 // 库存不足 end; /** * 购买商品Lua脚本 */ public boolean buyProduct(Long productId, Integer quantity) { String stockKey product:stock: productId; // ⚡ 执行Lua脚本原子操作 Long remainStock redisTemplate.execute( RedisScript.of(LUA_SCRIPT, Long.class), Collections.singletonList(stockKey), quantity.toString() ); if (remainStock null || remainStock 0) { log.warn(库存不足商品ID{}, productId); return false; } // 异步创建订单 orderService.createOrderAsync(productId, quantity); log.info(购买成功剩余库存{}, remainStock); return true; } } /** * Lua脚本的优势 * * ✅ 原子性整个脚本作为一个原子操作 * ✅ 减少网络往返一次请求完成多个操作 * ✅ 性能极高TPS可达10万 * ✅ 逻辑灵活可以实现复杂的业务逻辑 */实现3Redis 数据库最终一致性java体验AI代码助手代码解读复制代码/** * 完整的Redis数据库方案 * Redis扣减库存快 * DB订单数据持久化 * 异步同步库存到DB最终一致性 */ Service public class RedisDbStockService { Autowired private StringRedisTemplate redisTemplate; Autowired private ProductMapper productMapper; Autowired private OrderService orderService; Autowired private RabbitTemplate rabbitTemplate; /** * 购买商品完整流程 */ public boolean buyProduct(Long productId, Integer quantity) { // 1. ⚡ Redis扣减库存快速响应 String stockKey product:stock: productId; Long remainStock redisTemplate.opsForValue() .decrement(stockKey, quantity); if (remainStock null || remainStock 0) { // 库存不足 if (remainStock ! null remainStock 0) { redisTemplate.opsForValue().increment(stockKey, quantity); } return false; } // 2. 发送MQ消息异步处理订单 OrderMessage message OrderMessage.builder() .productId(productId) .quantity(quantity) .userId(getCurrentUserId()) .build(); rabbitTemplate.convertAndSend( order.exchange, order.create, message ); log.info(扣减Redis库存成功已发送MQ消息); return true; } /** * MQ消费者创建订单并同步库存到DB */ RabbitListener(queues order.create.queue) public void handleOrderCreate(OrderMessage message) { try { // 1. 创建订单 Order order orderService.createOrder( message.getProductId(), message.getQuantity(), message.getUserId() ); // 2. 扣减数据库库存最终一致性 int updated productMapper.decreaseStock( message.getProductId(), message.getQuantity() ); if (updated 0) { // DB库存不足理论上不应该出现 log.error(DB库存不足需要回滚); // 回滚Redis库存 String stockKey product:stock: message.getProductId(); redisTemplate.opsForValue().increment( stockKey, message.getQuantity()); // 取消订单 orderService.cancelOrder(order.getId()); } log.info(订单创建完成库存已同步到DB); } catch (Exception e) { log.error(处理订单失败, e); // 重试或人工介入 } } /** * 定时任务Redis库存同步到DB */ Scheduled(cron 0 */5 * * * ?) // 每5分钟 public void syncStockToDb() { // 扫描所有商品同步库存 ListProduct products productMapper.selectAll(); for (Product product : products) { String stockKey product:stock: product.getId(); String redisStock redisTemplate.opsForValue().get(stockKey); if (redisStock ! null) { int stock Integer.parseInt(redisStock); // 对比DB库存不一致则更新 if (stock ! product.getStock()) { productMapper.updateStockDirectly(product.getId(), stock); log.info(同步库存商品ID{}, Redis{}, DB{}-{}, product.getId(), stock, product.getStock(), stock); } } } } } 方案4消息队列削峰 - 流量控制 生活中的例子排队买奶茶没有队列所有人挤在柜台前混乱有队列排队等候一个个处理有序 技术实现java体验AI代码助手代码解读复制代码/** * MQ削峰方案 * * 流程 * 1. 用户下单 - 发送MQ消息 * 2. MQ消费者 - 串行处理订单 * 3. 扣减库存 - 创建订单 */ Service public class MqStockService { Autowired private RabbitTemplate rabbitTemplate; /** * 提交购买请求快速返回 */ public String submitBuyRequest(Long productId, Integer quantity) { // 生成订单ID String orderId generateOrderId(); // 发送MQ消息 OrderMessage message OrderMessage.builder() .orderId(orderId) .productId(productId) .quantity(quantity) .userId(getCurrentUserId()) .createTime(System.currentTimeMillis()) .build(); rabbitTemplate.convertAndSend( order.exchange, order.submit, message ); log.info(订单提交成功orderId{}, orderId); // ⚡ 立即返回订单ID用户可以轮询查询结果 return orderId; } /** * MQ消费者处理订单串行 */ RabbitListener(queues order.submit.queue, concurrency 1) // 单线程消费 public void handleOrderSubmit(OrderMessage message) { try { // 1. 检查库存 Product product productMapper.selectById(message.getProductId()); if (product.getStock() message.getQuantity()) { // 库存不足更新订单状态 orderService.updateOrderStatus( message.getOrderId(), OrderStatus.STOCK_INSUFFICIENT ); // 发送通知 notifyService.notifyStockInsufficient(message); return; } // 2. 扣减库存串行处理不会超卖 int updated productMapper.decreaseStock( message.getProductId(), message.getQuantity() ); if (updated 0) { // 扣减失败 orderService.updateOrderStatus( message.getOrderId(), OrderStatus.FAILED ); return; } // 3. 创建订单 orderService.createOrder(message); // 4. 更新订单状态 orderService.updateOrderStatus( message.getOrderId(), OrderStatus.SUCCESS ); // 5. 发送成功通知 notifyService.notifyOrderSuccess(message); log.info(订单处理成功orderId{}, message.getOrderId()); } catch (Exception e) { log.error(订单处理失败, e); // 更新订单状态 orderService.updateOrderStatus( message.getOrderId(), OrderStatus.FAILED ); } } /** * 查询订单状态 */ public OrderStatus queryOrderStatus(String orderId) { return orderService.getOrderStatus(orderId); } } /** * 优点 * ✅ 削峰填谷控制处理速度保护系统 * ✅ 异步处理用户体验好 * ✅ 解耦订单和库存系统解耦 * * 缺点 * ⚠️ 延迟用户需要等待处理结果 * ⚠️ 复杂度需要状态查询接口 * ⚠️ 消息可能丢失需要持久化 */ 方案5分布式锁 预扣库存 技术实现java体验AI代码助手代码解读复制代码/** * 分布式锁 预扣库存方案 * * 适用场景秒杀活动 * * 流程 * 1. 活动开始前预扣库存到Redis * 2. 用户抢购扣减Redis库存加分布式锁 * 3. 支付成功扣减真实库存 * 4. 支付超时回滚Redis库存 */ Service public class DistributedLockStockService { Autowired private RedissonClient redissonClient; Autowired private StringRedisTemplate redisTemplate; Autowired private ProductMapper productMapper; /** * 预扣库存活动开始前 */ public void preOccupyStock(Long productId, Integer stock) { // 1. 将库存加载到Redis String stockKey seckill:stock: productId; redisTemplate.opsForValue().set(stockKey, String.valueOf(stock)); // 2. 将真实库存记录下来 String realStockKey seckill:real_stock: productId; redisTemplate.opsForValue().set(realStockKey, String.valueOf(stock)); log.info(预扣库存完成商品ID{}, 库存{}, productId, stock); } /** * 抢购商品分布式锁 */ public String rushToBuy(Long productId, Integer quantity) { String lockKey seckill:lock: productId; RLock lock redissonClient.getLock(lockKey); try { // ⚡ 获取分布式锁最多等待3秒锁自动释放10秒 boolean isLocked lock.tryLock(3, 10, TimeUnit.SECONDS); if (!isLocked) { log.warn(获取锁失败商品ID{}, productId); return null; } // 1. 检查Redis库存 String stockKey seckill:stock: productId; Long remainStock redisTemplate.opsForValue() .decrement(stockKey, quantity); if (remainStock null || remainStock 0) { // 库存不足回滚 if (remainStock ! null remainStock 0) { redisTemplate.opsForValue().increment(stockKey, quantity); } log.warn(库存不足); return null; } // 2. 创建预订单待支付 String orderId orderService.createPreOrder(productId, quantity); // 3. 设置订单超时15分钟未支付自动取消 String orderKey seckill:order: orderId; redisTemplate.opsForValue().set( orderKey, orderId, Duration.ofMinutes(15) ); log.info(抢购成功orderId{}, 剩余库存{}, orderId, remainStock); return orderId; } catch (InterruptedException e) { log.error(获取锁被中断, e); return null; } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } /** * 支付成功扣减真实库存 */ Transactional(rollbackFor Exception.class) public boolean paySuccess(String orderId) { Order order orderService.getById(orderId); // 1. 扣减数据库真实库存 int updated productMapper.decreaseStock( order.getProductId(), order.getQuantity() ); if (updated 0) { log.error(扣减真实库存失败); return false; } // 2. 更新订单状态 orderService.updateOrderStatus(orderId, OrderStatus.PAID); // 3. 删除订单超时key String orderKey seckill:order: orderId; redisTemplate.delete(orderKey); log.info(支付成功真实库存已扣减); return true; } /** * 支付超时回滚Redis库存 */ Scheduled(fixedRate 60000) // 每分钟执行一次 public void handleExpiredOrders() { // 扫描过期订单 SetString keys redisTemplate.keys(seckill:order:*); if (keys null || keys.isEmpty()) { return; } for (String key : keys) { String orderId redisTemplate.opsForValue().get(key); if (orderId ! null) { continue; // 订单未过期 } // 订单已过期回滚库存 Order order orderService.getById(extractOrderId(key)); if (order ! null order.getStatus() OrderStatus.PENDING) { // 回滚Redis库存 String stockKey seckill:stock: order.getProductId(); redisTemplate.opsForValue().increment( stockKey, order.getQuantity()); // 取消订单 orderService.cancelOrder(order.getId()); log.info(订单超时已回滚库存orderId{}, order.getId()); } } } } /** * 优点 * ✅ 高性能Redis操作快 * ✅ 支持预扣适合秒杀场景 * ✅ 自动回滚超时自动释放库存 * * 缺点 * ⚠️ 复杂度高需要处理超时、回滚 * ⚠️ 数据一致性Redis和DB可能不一致 */ 方案对比总结方案性能(TPS)并发度一致性复杂度适用场景悲观锁DB67 低强一致简单低并发乐观锁DB500 ✅中强一致中等中并发Redis原子操作10000 高最终一致中等高并发推荐MQ削峰5000 ✅高最终一致高超高并发分布式锁预扣8000 高最终一致高秒杀活动✅ 最佳实践markdown体验AI代码助手代码解读复制代码生产环境推荐方案Redis MQ 数据库 架构设计 □ L1Redis扣减库存快速响应TPS1万 □ L2MQ异步处理订单削峰填谷 □ L3数据库持久化最终一致性 关键要点 □ 库存预热提前加载到Redis □ 原子操作使用Lua脚本 □ 异步处理MQ削峰 □ 降级策略限流、熔断 □ 监控告警库存、延迟、失败率 极端场景处理 □ Redis宕机 - 降级到数据库 □ MQ堵塞 - 限流保护 □ 数据不一致 - 定时对账 □ 恶意刷单 - 黑名单、验证码 总结核心要点体验AI代码助手代码解读复制代码防止超卖的本质保证库存扣减的原子性 五大方案 1️⃣ 悲观锁简单但性能差 2️⃣ 乐观锁性能中等有重试 3️⃣ Redis原子操作高性能推荐⭐⭐⭐⭐⭐ 4️⃣ MQ削峰异步处理削峰填谷 5️⃣ 分布式锁预扣适合秒杀 推荐方案Redis MQ 数据库记住防超卖的核心是原子操作异步处理最终一致性