{"record":{"id":"be9130a414c0bf5e","repo":"alibaba/spring-cloud-alibaba","slug":"deduct-stock-failed","errorCode":null,"errorMessage":"deduct stock failed","messagePattern":"deduct stock failed","errorType":"exception","errorClass":"BusinessException","httpStatus":null,"severity":"error","filePath":"spring-cloud-alibaba-examples/integrated-example/integrated-storage/src/main/java/com/alibaba/cloud/integration/storage/service/impl/StorageServiceImpl.java","lineNumber":55,"sourceCode":"public class StorageServiceImpl implements StorageService {\n\n\tprivate Logger logger = LoggerFactory.getLogger(getClass());\n\n\t@Autowired\n\tprivate StorageMapper storageMapper;\n\n\t@Override\n\t@Transactional\n\tpublic void reduceStock(String commodityCode, Integer count)\n\t\t\tthrows BusinessException {\n\t\tlogger.info(\"[reduceStock] current XID: {}\", RootContext.getXID());\n\n\t\tcheckStock(commodityCode, count);\n\n\t\tTimestamp updateTime = new Timestamp(System.currentTimeMillis());\n\t\tint updateCount = storageMapper.reduceStock(commodityCode, count, updateTime);\n\t\tif (updateCount == 0) {\n\t\t\tthrow new BusinessException(\"deduct stock failed\");\n\t\t}\n\t}\n\n\t@Override\n\tpublic Result<?> getRemainCount(String commodityCode) {\n\t\tInteger stock = storageMapper.getStock(commodityCode);\n\t\tif (stock == null) {\n\t\t\treturn Result.failed(\"commodityCode wrong,please check commodity code\");\n\t\t}\n\t\treturn Result.success(stock);\n\t}\n\n\tprivate void checkStock(String commodityCode, Integer count)\n\t\t\tthrows BusinessException {\n\t\tInteger stock = storageMapper.getStock(commodityCode);\n\t\tif (stock < count) {\n\t\t\tthrow new BusinessException(\"no enough stock\");\n\t\t}","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/alibaba/spring-cloud-alibaba/blob/115d5901102009492e05d5ec18c3f79cad4077d0/spring-cloud-alibaba-examples/integrated-example/integrated-storage/src/main/java/com/alibaba/cloud/integration/storage/service/impl/StorageServiceImpl.java#L37-L73","documentation":"Thrown inside StorageServiceImpl.reduceStock (@Transactional Seata branch) when storageMapper.reduceStock(...) returns 0 affected rows. The UPDATE is `UPDATE storage SET count = count - #{count}, update_time=#{updateTime} WHERE commodity_code = #{commodityCode}` — note it has NO count-guard, so a 0-row result means only one thing: no storage row matches the commodityCode. (Unlike the account UPDATE, stock can go negative here; this exception is not about insufficient stock, that is checked separately in checkStock.)","triggerScenarios":"reduceStock(commodityCode, count) invoked for a commodityCode that does not exist in the storage table; the row was deleted between checkStock and the UPDATE; the commodityCode string does not match (whitespace/case mismatch).","commonSituations":"Order requests with a commodityCode not present in the seeded storage data; storage table not initialized (DDL/DML not run); a typo or trailing whitespace in the commodityCode.","solutions":["Confirm the row exists: SELECT count FROM storage WHERE commodity_code = '<commodityCode>'.","Seed the missing commodity: INSERT INTO storage (commodity_code, count) VALUES ('<commodityCode>', <units>).","Trim/normalize the commodityCode at the controller boundary and verify it matches the stored value exactly."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before reduceStock, confirm the commodity row exists (the UPDATE has no count guard).\nInteger stock = storageMapper.getStock(commodityCode);\nif (stock == null) {\n    // commodity missing -> avoid a 0-row update that throws 'deduct stock failed'\n    throw new BusinessException(\"commodity not found: \" + commodityCode);\n}","typeGuard":null,"tryCatchPattern":"try {\n    storageService.reduceStock(commodityCode, count);\n} catch (BusinessException e) {\n    if (\"deduct stock failed\".equals(e.getMessage())) {\n        // no matching commodityCode row; fix the data/seed\n    }\n    throw e;\n}","preventionTips":["Ensure every orderable commodityCode has a seeded storage row.","Normalize commodityCode (trim) at the controller boundary.","Remember the storage UPDATE matches on commodityCode only — there is no count guard."],"tags":["seata","distributed-transaction","database","stock","not-found"],"backgroundTag":null,"analyzedSha":"115d5901102009492e05d5ec18c3f79cad4077d0","analyzedAt":"2026-08-14T04:47:13.900Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}