You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot 3+MongoDB销售系统库存一致性保障方案咨询

解决Spring Boot 3 + MongoDB 非响应式架构下的库存超卖问题

针对你遇到的库存超卖问题,结合Spring Boot 3 + MongoDB的非响应式架构,给你几个实用的解决方案:


1. 利用MongoDB单文档原子更新(最推荐)

MongoDB的单文档读写操作本身是原子性的,无需依赖事务就能保证并发下的库存一致性。核心思路是把「校验库存充足」和「扣减库存」合并成一个原子操作,从根源避免并发冲突。

代码示例:

先定义商品实体:

@Document(collection = "products")
public class Product {
    @Id
    private String id;
    private String name;
    private int stock; // 库存数量
    // getter、setter、构造方法
}

在Service层实现原子扣减逻辑:

@Service
public class StockService {
    private final MongoTemplate mongoTemplate;

    public StockService(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }

    // 尝试扣减指定商品库存,返回操作是否成功
    public boolean decreaseStock(String productId) {
        // 查询条件:商品ID匹配,且库存 > 0
        Query query = Query.query(Criteria.where("id").is(productId).and("stock").gt(0));
        // 更新操作:库存减1
        Update update = Update.update("stock", new IncOperator(-1));

        // 执行原子更新,返回更新后的文档(更新成功则非空)
        Product updatedProduct = mongoTemplate.findAndModify(query, update, Product.class);

        return updatedProduct != null;
    }
}

下单逻辑中调用:

@Service
public class OrderService {
    private final StockService stockService;

    public OrderService(StockService stockService) {
        this.stockService = stockService;
    }

    public String createOrder(String productId) {
        if (stockService.decreaseStock(productId)) {
            // 库存扣减成功,执行创建订单逻辑
            return "订单创建成功";
        } else {
            return "库存不足,下单失败";
        }
    }
}

这个方案完全依托MongoDB原生特性,无需额外依赖,性能优异,适合绝大多数场景。


2. 乐观锁机制

如果你的业务允许少量冲突重试,可以用乐观锁实现。给商品实体添加版本号字段,每次更新时校验版本号,只有版本匹配才执行更新,冲突时抛出异常,业务层可选择重试或返回失败。

代码示例:

修改商品实体,添加@Version注解:

@Document(collection = "products")
public class Product {
    @Id
    private String id;
    private String name;
    private int stock;
    @Version
    private Long version; // 乐观锁版本号
    // getter、setter、构造方法
}

Service层扣减逻辑:

@Service
public class StockService {
    private final ProductRepository productRepository;

    public StockService(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    public boolean decreaseStock(String productId) {
        Optional<Product> productOpt = productRepository.findById(productId);
        if (productOpt.isEmpty() || productOpt.get().getStock() <= 0) {
            return false;
        }

        Product product = productOpt.get();
        product.setStock(product.getStock() - 1);

        try {
            productRepository.save(product);
            return true;
        } catch (OptimisticLockingFailureException e) {
            // 版本冲突,说明有其他请求先更新了库存
            return false;
        }
    }
}

若并发较高,可在业务层添加重试逻辑(比如用Spring的@Retryable):

@Retryable(value = OptimisticLockingFailureException.class, maxAttempts = 3)
public boolean decreaseStockWithRetry(String productId) {
    return decreaseStock(productId);
}

这个方案实现简单,无需额外中间件,适合并发不是特别高的场景。


3. 应用层分布式锁(多实例部署场景)

如果你的应用是多实例部署,想要更严格的并发控制(虽然单文档原子更新已足够,但部分场景可能需要),可以引入分布式锁(比如基于Redis),确保同一时间只有一个请求能操作某个商品的库存。

代码示例(基于Spring Data Redis):

先实现分布式锁工具类:

@Service
public class RedisDistributedLock {
    private final StringRedisTemplate stringRedisTemplate;
    private static final String LOCK_PREFIX = "stock:lock:";
    private static final long LOCK_EXPIRE = 30000; // 锁过期时间30秒

    public RedisDistributedLock(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    // 尝试获取锁
    public boolean tryLock(String productId) {
        String lockKey = LOCK_PREFIX + productId;
        // 用SETNX原子命令获取锁
        Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "locked", LOCK_EXPIRE, TimeUnit.MILLISECONDS);
        return Boolean.TRUE.equals(success);
    }

    // 释放锁
    public void unlock(String productId) {
        String lockKey = LOCK_PREFIX + productId;
        stringRedisTemplate.delete(lockKey);
    }
}

结合分布式锁和原子更新逻辑:

@Service
public class StockService {
    private final MongoTemplate mongoTemplate;
    private final RedisDistributedLock redisDistributedLock;

    public StockService(MongoTemplate mongoTemplate, RedisDistributedLock redisDistributedLock) {
        this.mongoTemplate = mongoTemplate;
        this.redisDistributedLock = redisDistributedLock;
    }

    public boolean decreaseStock(String productId) {
        // 先获取分布式锁
        if (!redisDistributedLock.tryLock(productId)) {
            // 获取锁失败,直接返回库存不足或触发重试
            return false;
        }

        try {
            // 执行原子更新库存操作
            Query query = Query.query(Criteria.where("id").is(productId).and("stock").gt(0));
            Update update = Update.update("stock", new IncOperator(-1));
            Product updatedProduct = mongoTemplate.findAndModify(query, update, Product.class);
            return updatedProduct != null;
        } finally {
            // 无论成功失败,释放锁
            redisDistributedLock.unlock(productId);
        }
    }
}

注意:分布式锁要设置合理的过期时间避免死锁,且锁的粒度要细(按商品ID加锁),不要用全局锁影响性能。


补充说明

你提到MongoDB不支持事务,其实MongoDB 4.0及以上版本支持多文档事务,但需要部署副本集或分片集群。不过对于库存扣减这种单文档操作,完全不需要事务,单文档原子更新已经足够,且性能更好。只有涉及多文档操作(比如扣减库存同时生成订单记录)时,才需要用到事务,前提是你的MongoDB环境支持。

内容的提问来源于stack exchange,提问作者Carlos C.M

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 07:02:42