crossoverJie/JCSprout · warning · RuntimeException

库存不足

Error message

库存不足

What it means

Thrown by checkStock() in the 'wrong order' seconds-kill implementation when stock.getSale() equals stock.getCount() — i.e. every unit of inventory has been sold. This is the naive, non-thread-safe implementation: it reads stock, compares sale vs count, then updates in a separate call, which is exactly the check-then-act gap that causes overselling under concurrency. The exception means stock is exhausted according to the DB read at this instant.

Source

Thrown at MD/third-party-component/seconds-kill.md:149

    @Override
    public int createWrongOrder(int sid) throws Exception{

        //校验库存
        Stock stock = checkStock(sid);

        //扣库存
        saleStock(stock);

        //创建订单
        int id = createOrder(stock);

        return id;
    }
    
    private Stock checkStock(int sid) {
        Stock stock = stockService.getStockById(sid);
        if (stock.getSale().equals(stock.getCount())) {
            throw new RuntimeException("库存不足");
        }
        return stock;
    }
    
    private int saleStock(Stock stock) {
        stock.setSale(stock.getSale() + 1);
        return stockService.updateStockById(stock);
    }
    
    private int createOrder(Stock stock) {
        StockOrder order = new StockOrder();
        order.setSid(stock.getId());
        order.setName(stock.getName());
        int id = orderMapper.insertSelective(order);
        return id;
    }        
        
}

View on GitHub (pinned to fc4c6e5f6d)

Solutions

  1. Switch to the optimistic-lock variant (createOptimisticOrder) which uses a version column to prevent overselling atomically.
  2. Fix the equality check to >= so it catches the oversold case where sale has already exceeded count.
  3. Pre-load stock counts into Redis and check there first to reduce DB pressure (the Redis-cached variant).
  4. Handle the exception at the controller level and return a user-friendly 'out of stock' response.

Example fix

// before — only catches exact equality, not oversold state
if (stock.getSale().equals(stock.getCount())) {
    throw new RuntimeException("库存不足");
}

// after — catches exhaustion and oversold corruption
if (stock.getSale() >= stock.getCount()) {
    throw new RuntimeException("库存不足");
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check stock before calling the order service to give an early rejection.
Stock stock = stockService.getStockById(sid);
if (stock.getSale() >= stock.getCount()) {
    throw new RuntimeException("库存不足");
}
// Still call the service — the real guard must be the DB-level lock.
int orderId = orderService.createOptimisticOrder(sid);

Try / catch

try {
    int orderId = orderService.createWrongOrder(sid);
    return ResponseEntity.ok(orderId);
} catch (RuntimeException e) {
    if ("库存不足".equals(e.getMessage())) {
        return ResponseEntity.status(409).body("Sold out");
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling createWrongOrder(sid) when the stock table shows sale == count for that sid. Under concurrent load this check is unreliable — multiple threads read sale < count simultaneously and all proceed past the check before any has written sale+1, producing oversell. After overselling exhausts stock, subsequent calls correctly hit this exception.

Common situations: Inventory fully consumed during a flash sale. The stock row was never initialised or was set to count=0. A previous overselling bug has already driven sale beyond count, so the .equals() check (which only catches exact equality) may miss it — note the bug: it should be >= not ==.

Related errors


AI-assisted analysis of crossoverJie/JCSprout@fc4c6e5f6d (2026-08-14). Data as JSON: /api/errors/730fd3fcc2946cb0. Report an issue: GitHub.