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
- Switch to the optimistic-lock variant (createOptimisticOrder) which uses a version column to prevent overselling atomically.
- Fix the equality check to >= so it catches the oversold case where sale has already exceeded count.
- Pre-load stock counts into Redis and check there first to reduce DB pressure (the Redis-cached variant).
- 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
- Use the optimistic-lock variant (createOptimisticOrder) instead of the naive createWrongOrder to prevent overselling.
- Fix the equality check from == to >= so it catches already-oversold state.
- Handle the exception at the controller level and return a user-friendly 'out of stock' response rather than a 500.
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.