yudaocode/SpringBoot-Labs · error · java.lang.Exception
库存不足
Error message
库存不足
What it means
ProductServiceImpl.reduceStock throws Exception('库存不足' — insufficient stock) when the stock-decrement UPDATE affects 0 rows. Under Seata AT this participant failure rolls back the whole global purchase transaction (order creation, balance deduction, etc.). Zero affected rows means the conditional UPDATE (stock >= amount guard) found no matching row — stock exhausted or a concurrent purchase consumed it first.
Source
Thrown at lab-52/lab-52-multiple-datasource/src/main/java/cn/iocoder/springboot/lab52/seatademo/service/impl/ProductServiceImpl.java:37
@Autowired
private ProductDao productDao;
@Override
@DS(value = "product-ds")
@Transactional(propagation = Propagation.REQUIRES_NEW) // 开启新事物
public void reduceStock(Long productId, Integer amount) throws Exception {
logger.info("[reduceStock] 当前 XID: {}", RootContext.getXID());
// 检查库存
checkStock(productId, amount);
logger.info("[reduceStock] 开始扣减 {} 库存", productId);
// 扣减库存
int updateCount = productDao.reduceStock(productId, amount);
// 扣除成功
if (updateCount == 0) {
logger.warn("[reduceStock] 扣除 {} 库存失败", productId);
throw new Exception("库存不足");
}
// 扣除失败
logger.info("[reduceStock] 扣除 {} 库存成功", productId);
}
private void checkStock(Long productId, Integer requiredAmount) throws Exception {
logger.info("[checkStock] 检查 {} 库存", productId);
Integer stock = productDao.getStock(productId);
if (stock < requiredAmount) {
logger.warn("[checkStock] {} 库存不足,当前库存: {}", productId, stock);
throw new Exception("库存不足");
}
}
}
View on GitHub (pinned to 6c12efaed0)
Solutions
- Treat the error as correct oversell protection: retry with a lower amount or restock the product.
- Keep the atomic guard in SQL (UPDATE ... WHERE id=? AND stock >= ?) and translate 0 rows to a typed InsufficientStockException.
- For hot products, add Redis/DB row locks or queue-based stock deduction to reduce contention instead of weakening the guard.
- Confirm Seata DataSourceProxy wraps the product datasource so a throw triggers branch rollback (check undo_log table entries).
Example fix
// before
int updateCount = productDao.reduceStock(productId, amount);
if (updateCount == 0) {
throw new Exception("库存不足");
}
// after
int updated = productDao.reduceStockIfEnough(productId, amount);
// UPDATE product SET stock = stock - #{amount} WHERE id = #{productId} AND stock >= #{amount}
if (updated == 0) {
throw new InsufficientStockException(productId);
} Defensive patterns
Strategy: validation
Validate before calling
// Atomic stock check-and-decrement in one statement:
// UPDATE product SET stock = stock - #{amount} WHERE id = #{productId} AND stock >= #{amount}
int ok = productDao.reduceStockIfEnough(productId, amount);
if (ok == 0) { /* structured rejection, no exception */ } Try / catch
try {
productService.reduceStock(productId, amount);
} catch (Exception e) {
throw new OrderFailedException("INSUFFICIENT_STOCK", e);
} Prevention
- Never remove the stock >= amount SQL guard to silence this error — it prevents oversell.
- Expect this error in load tests as stock depletes; assert rollback of the order row.
- Use typed exceptions carrying productId for observability.
When it happens
Trigger: POST /product/reduce-stock (productId, amount) where the conditional UPDATE matches no row: stock lower than amount, product id absent, or a race with a concurrent purchase. checkStock earlier catches the static case; this branch catches races and missing rows.
Common situations: Oversell protection in flash-sale demos. Common follow-up issues: devs remove the WHERE stock >= #{amount} guard to 'fix' the error and thereby allow oversell (negative stock); or under Seata, the branch transaction's undo_log is left behind if the datasource is not proxied, and the throw no longer rolls back other participants.
Related errors
AI-assisted analysis of yudaocode/SpringBoot-Labs@6c12efaed0 (2026-08-14).
Data as JSON: /api/errors/5ce56ac801605808.
Report an issue: GitHub.