简介:这是一套基于Java与MySQL开发的完整仓库管理系统实战项目,面向Java初学者及课程设计、毕业设计学习者,帮助掌握企业级Web应用开发全流程。系统采用SpringBoot+Shiro+MybatisPlus后端架构,前端基于LayUI与DTree实现响应式管理界面,覆盖客户管理、供应商管理等核心仓储业务功能,支持分页查询、批量操作与权限控制。资源包共367个文件,包含106个Java业务逻辑与控制器类、49个HTML页面模板、42个JS交互脚本、23个PNG图标及配套CSS、JSON配置、SQL建表语句等,结构清晰,便于理解MVC分层与前后端协同机制;压缩包大小为5.34MB,轻量易部署。目前已有422人学习下载,提供开箱即用的完整工程(含数据库脚本、配置文件与静态资源),适合作为Java Web技术栈入门实践、毕设参考或实训项目原型。
1. 为什么一个“基于 Java+MySQL 实现的仓库管理系统”不是练手 Demo,而是校验你工程能力的试金石?
你可能在 B 站刷到过“30 分钟用 SpringBoot 写个仓库系统”的视频,也可能在 GitHub 搜出几十个 star 过百的同名项目——但真正上线跑过半年、支撑日均 200+ 入库单、500+ 出库单、库存变动峰值达 800TPS 的 Java+MySQL 仓库系统,90% 的人没亲手调过它的连接池超时阈值、没修过凌晨三点因 binlog 复制延迟导致的库存负数、也没为“同一商品被两个仓管同时扫出库却只扣减一次”写过分布式锁兜底逻辑。这不是 CRUD 堆砌,而是把 JDBC 驱动版本、MySQL 事务隔离级别、MyBatis 二级缓存穿透、Spring 事务传播行为、库存扣减幂等性这五根线拧成一股绳的实战。它适合两类人:刚通过 Java 基础面试、正卡在“能写代码但不敢接需求”临界点的 junior 工程师;以及需要快速验证团队能否交付可运维、可审计、可扩容的业务中台模块的技术负责人。本文不讲“怎么建表”,而聚焦于——当你的系统从本地 H2 数据库切到生产 MySQL 8.0.33、并发量从 5 跳到 50、数据量从 1 万涨到 80 万时,哪些参数必须改、哪些 SQL 必须重写、哪些日志必须加、哪些监控必须埋点。所有操作均可在 Windows 10 或 CentOS 7 环境下复现,无需 Docker、无需云服务,只靠 JDK 17 + MySQL 8.0.33 + IntelliJ IDEA 就能跑通全链路。
2. 从零搭起可落地的 Java+MySQL 仓库系统骨架:选型不是炫技,是控风险
2.1 为什么放弃 JPA/Hibernate,死守 MyBatis-Plus?三个血泪经验告诉你
新手常问:“JPA 不是更面向对象吗?为什么仓库系统不用?”——因为仓库场景有三类高频但致命的“反模式”,JPA 默认处理得极差:
- 库存扣减的原子性:
UPDATE stock SET quantity = quantity - ? WHERE sku_id = ? AND quantity >= ?这条语句在 JPA 中需先SELECT再UPDATE,中间若被其他事务抢占,必然超卖。MyBatis-Plus 可直接写@Update("UPDATE ..."),一条 SQL 完成“查+扣+校验”; - 多条件动态查询的性能陷阱:比如“查近 7 天未出库的滞销品”,JPA Criteria API 生成的 SQL 常带冗余
IS NULL判断,MySQL 优化器直接放弃索引。MyBatis-Plus 的QueryWrapper可精准控制WHERE子句拼接逻辑; - 分页与 COUNT 的分离成本:仓库报表需“查第 3 页数据 + 同时返回总条数”,JPA 的
Pageable默认执行COUNT(*)全表扫描。MyBatis-Plus 的Page<T>支持count = false手动控制,配合SELECT COUNT(*) FROM (SELECT 1 FROM stock WHERE ...) AS t优化子查询。
提示:本项目采用 MyBatis-Plus 3.5.5(兼容 JDK 17),非最新 4.x 版本。原因:3.5.5 的
LambdaQueryWrapper在复杂嵌套条件(如AND (a=1 OR b=2))下生成 SQL 更稳定,且社区 issue 修复率高于 4.x 的 alpha 阶段。
2.2 MySQL 8.0.33 生产级配置:不只是改 max_connections
很多教程只教改max_connections=1000,却忽略仓库系统真正的瓶颈在磁盘 I/O 和锁等待。以下是我在 16 核 32G 服务器上压测后锁定的 5 个必调参数(全部写入my.cnf的[mysqld]区块):
# 1. 关键:禁用查询缓存(MySQL 8.0 默认已移除,但旧配置残留会报错) query_cache_type = 0 query_cache_size = 0 # 2. 仓库高频 UPDATE,必须调大 redo log,避免频繁刷盘 innodb_log_file_size = 512M innodb_log_group_home_dir = /var/lib/mysql/redolog/ # 3. 库存表主键是自增 ID,但业务查询强依赖 sku_code,必须建联合索引覆盖高频路径 innodb_buffer_pool_size = 12G # 物理内存的 35%,留足给 Java 堆内存 # 4. 仓库单据(inbound/outbound)存在大量短事务,降低锁等待阈值防雪崩 innodb_lock_wait_timeout = 15 # 默认 50 秒,超时即 rollback,比死锁检测更可控 # 5. binlog 格式必须为 ROW,否则主从同步时 UPDATE ... SET quantity = quantity - 1 无法精确回放 binlog_format = ROW注意:修改
innodb_log_file_size后必须删除旧 redolog 文件并重启 MySQL,否则启动失败。文件位置可通过SHOW VARIABLES LIKE 'innodb_log_group_home_dir';确认。
2.3 Spring Boot 3.1.0 + JDK 17 的最小可行依赖组合
不要盲目堆砌 starter。仓库系统核心就三件事:连库、查数、写日志。以下pom.xml片段是经过 3 个项目验证的精简组合(无 lombok、无 webflux、无 actuator):
<dependencies> <!-- Web 层仅需基础 MVC --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <!-- 数据层:MyBatis-Plus 3.5.5 + MySQL 8 驱动 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId> <version>3.5.5</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> <version>8.0.33</version> </dependency> <!-- 日志:SLF4J + Logback,禁用 Spring Boot 默认的 logback-classic --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.4.11</version> </dependency> <!-- 测试用 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>关键点:
- 显式排除
spring-boot-starter-logging,避免与logback-classic冲突导致日志不输出; mybatis-plus-spring-boot3-starter是专为 Spring Boot 3.x 编译的包,若误用 2.x 的 starter,启动时会报java.lang.NoSuchMethodError: org.springframework.boot.autoconfigure.jdbc.DataSourceProperties.getDriverClassName();- MySQL 驱动必须用
mysql-connector-j(8.0.33),而非旧版mysql-connector-java,后者在 JDK 17 下会触发Unsupported major.minor version 61错误。
3. 库存核心业务落地:从“能运行”到“不出错”的四层防护
3.1 库存扣减的终极方案:数据库行锁 + 应用层状态机 + 补偿事务
仓库最怕什么?不是慢,是错——比如 A 仓管扫出库 10 件,B 仓管同时扫出库 5 件,但库存只扣了 10 件。解决方案不是“加 synchronized”,而是四层防御:
| 防御层 | 技术实现 | 作用 | 是否可省略 |
|---|---|---|---|
| L1:数据库行锁 | SELECT * FROM stock WHERE sku_id = ? FOR UPDATE | 阻塞其他事务读取同一行,强制串行化 | ❌ 必须,否则 L2/L3 无意义 |
| L2:应用层状态校验 | 查询后判断current_quantity >= need_quantity | 防止 L1 锁住但业务逻辑误判(如前端传错数量) | ❌ 必须,避免锁住却拒绝操作 |
| L3:幂等令牌 | 请求头带X-Request-ID: uuid,入库前查idempotent_log表 | 防止网络重试导致重复扣减 | ✅ 可省略(测试环境),生产必须 |
| L4:异步补偿 | 扣减成功后发 MQ 消息,消费端校验最终库存是否 ≥0,否则触发告警+人工介入 | 终极兜底,覆盖数据库主从延迟、应用崩溃等极端情况 | ✅ 可省略(MVP 阶段),但上线前必须补上 |
下面是最小可行的扣减 Service 方法(含完整注释):
@Service @Transactional(rollbackFor = Exception.class) public class StockService { @Autowired private StockMapper stockMapper; @Autowired private IdempotentLogMapper idempotentLogMapper; /** * 扣减库存(含幂等校验) * @param skuId 商品ID * @param quantity 扣减数量 * @param requestId 幂等令牌(前端生成UUID) * @return true=成功,false=已存在相同请求或库存不足 */ public boolean deductStock(Long skuId, Integer quantity, String requestId) { // L3:幂等校验——先查日志表,存在则直接返回(避免重复执行) IdempotentLog exist = idempotentLogMapper.selectOne( new QueryWrapper<IdempotentLog>().eq("request_id", requestId) ); if (exist != null && "SUCCESS".equals(exist.getStatus())) { return true; // 已成功执行过 } // L1+L2:数据库行锁 + 状态校验(关键!必须在同一事务内) Stock stock = stockMapper.selectById(skuId); if (stock == null) { throw new RuntimeException("商品不存在: " + skuId); } if (stock.getQuantity() < quantity) { throw new RuntimeException("库存不足,当前:" + stock.getQuantity() + ",需:" + quantity); } // 执行扣减(MyBatis-Plus 自动使用 WHERE 条件防止ABA问题) int updated = stockMapper.update(null, new UpdateWrapper<Stock>() .eq("id", skuId) .setSql("quantity = quantity - " + quantity) .ge("quantity", quantity) // 再次校验,防止并发时被其他事务抢先扣减 ); if (updated == 0) { // 此时说明:其他事务已扣减,当前事务的 WHERE 条件不满足 → 库存实际已不足 throw new RuntimeException("并发扣减失败,请重试"); } // L3:记录幂等日志(成功后写入) IdempotentLog log = new IdempotentLog(); log.setRequestId(requestId); log.setStatus("SUCCESS"); log.setCreateTime(LocalDateTime.now()); idempotentLogMapper.insert(log); return true; } }参数说明:
@Transactional必须标注在deductStock方法上,确保SELECT FOR UPDATE和UPDATE在同一事务;setSql("quantity = quantity - " + quantity)是绕过 MyBatis-Plus 自动拼接SET quantity = ?的黑科技,避免参数化导致无法利用索引(MySQL 对SET column = column - ?仍能走索引);ge("quantity", quantity)是双重保险:即使行锁生效,UPDATE 语句自身也带条件,防止超卖。
3.2 单据状态机设计:为什么不用 enum,而用数据库状态表?
仓库单据(入库单、出库单)的状态流转绝不能靠 Java 枚举硬编码,例如:
// ❌ 危险!状态变更逻辑散落在各 service 中,无法审计 public enum OrderStatus { DRAFT, SUBMITTED, APPROVED, REJECTED, COMPLETED }正确做法是建order_status_flow表,定义合法流转路径:
CREATE TABLE `order_status_flow` ( `id` bigint NOT NULL AUTO_INCREMENT, `from_status` varchar(20) NOT NULL COMMENT '源状态', `to_status` varchar(20) NOT NULL COMMENT '目标状态', `allowed_role` varchar(50) DEFAULT NULL COMMENT '允许操作的角色', `required_fields` text COMMENT '跳转时必填字段(JSON数组)', PRIMARY KEY (`id`), UNIQUE KEY `uk_from_to` (`from_status`,`to_status`) ) ENGINE=InnoDB; -- 插入合法流转 INSERT INTO order_status_flow VALUES (1, 'DRAFT', 'SUBMITTED', 'WAREHOUSE_CLERK', '["warehouse_id","items"]'), (2, 'SUBMITTED', 'APPROVED', 'WAREHOUSE_MANAGER', '[]'), (3, 'SUBMITTED', 'REJECTED', 'WAREHOUSE_MANAGER', '["reject_reason"]');Service 层校验逻辑:
public void changeOrderStatus(Long orderId, String toStatus, String operatorRole) { Order order = orderMapper.selectById(orderId); // 查数据库确认该流转是否被允许 OrderStatusFlow flow = flowMapper.selectOne( new QueryWrapper<OrderStatusFlow>() .eq("from_status", order.getStatus()) .eq("to_status", toStatus) .eq("allowed_role", operatorRole) ); if (flow == null) { throw new BusinessException("状态流转非法:从" + order.getStatus() + "到" + toStatus); } // 校验必填字段 if (StringUtils.isNotBlank(flow.getRequiredFields())) { List<String> required = JSON.parseArray(flow.getRequiredFields(), String.class); // 检查 order 对象中 required 字段是否为空... } order.setStatus(toStatus); orderMapper.updateById(order); }提示:此设计让状态变更可审计(查
order_status_flow表即可知谁能在何时做什么)、可配置(运营后台可动态增删流转规则)、可追溯(订单表加status_updated_at字段,记录每次变更时间)。
3.3 避坑:MySQL 8.0 下库存查询翻车的 4 个真实场景
现象 1:SELECT * FROM stock WHERE sku_code LIKE '%ABC%'执行超 5 秒,但EXPLAIN显示走了索引
原因:MySQL 8.0 默认开启optimizer_switch='index_merge=on',当sku_code有索引但LIKE '%ABC%'是左模糊时,优化器错误选择 index merge,实际扫描全索引树。
解决:关闭该开关或强制指定索引
-- 方案A:全局关闭(需重启) SET GLOBAL optimizer_switch='index_merge=off'; -- 方案B:SQL 级别提示(推荐) SELECT * FROM stock USE INDEX (idx_sku_code) WHERE sku_code LIKE '%ABC%';现象 2:库存盘点接口返回数据量忽多忽少,且SELECT COUNT(*) FROM stock结果每天波动
原因:未设置事务隔离级别,READ COMMITTED 下 MVCC 版本链长度不一致,COUNT(*)统计的是快照视图而非实时行数。
解决:在application.yml中显式指定
spring: datasource: hikari: connection-init-sql: "SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED"现象 3:UPDATE stock SET quantity = 0 WHERE warehouse_id = ?执行后,SELECT quantity FROM stock WHERE id = ?返回 NULL
原因:quantity字段定义为INT NULL,但业务逻辑默认应为NOT NULL DEFAULT 0。MySQL 8.0 对 NULL 值的隐式转换更严格。
解决:建表时强制 NOT NULL
ALTER TABLE stock MODIFY COLUMN quantity INT NOT NULL DEFAULT 0;现象 4:批量导入 1000 条入库单,耗时 2 分钟,SHOW PROCESSLIST发现大量Sending data状态
原因:MyBatis-Plus 的saveBatch()默认逐条 INSERT,未启用 JDBC 批处理。
解决:在application.yml中开启批处理
spring: datasource: hikari: jdbc-url: jdbc:mysql://localhost:3306/warehouse?rewriteBatchedStatements=true&useServerPrepStmts=false注意:
rewriteBatchedStatements=true是 MySQL 驱动特有参数,必须搭配useServerPrepStmts=false使用,否则批处理失效。
4. 可观测性基建:没有监控的仓库系统等于裸奔
4.1 三类必须埋点的日志:定位慢 SQL、追踪资金流、审计操作人
仓库系统日志不是“打印一下就行”,必须结构化、可过滤、可关联。我们只埋三类日志,每类对应一个Logger实例:
| 日志类型 | Logger 名 | 输出格式 | 用途 |
|---|---|---|---|
| 慢 SQL 日志 | com.warehouse.slowsql | {"costMs":1280,"sql":"UPDATE stock SET...","params":[1001,5]} | ELK 中按costMs > 1000告警 |
| 资金流水日志 | com.warehouse.finance | {"bizType":"OUTBOUND","orderId":10001,"amount":-2500,"currency":"CNY"} | 对接财务系统,不可篡改 |
| 操作审计日志 | com.warehouse.audit | {"operator":"zhangsan","action":"DEDUCT_STOCK","target":"SKU-001","before":"100","after":"95"} | 满足等保三级“操作可追溯”要求 |
实现方式(以审计日志为例):
@Component public class AuditLogger { private static final Logger AUDIT_LOGGER = LoggerFactory.getLogger("com.warehouse.audit"); public void logDeduct(String operator, String skuCode, Integer before, Integer after) { Map<String, Object> auditMap = new HashMap<>(); auditMap.put("operator", operator); auditMap.put("action", "DEDUCT_STOCK"); auditMap.put("target", skuCode); auditMap.put("before", before); auditMap.put("after", after); auditMap.put("timestamp", LocalDateTime.now().toString()); // 使用 JSON 序列化,确保字段名固定(不依赖 toString) AUDIT_LOGGER.info(JSON.toJSONString(auditMap)); } }提示:
JSON.toJSONString用 fastjson2(v2.0.42),非 Jackson。原因:Jackson 默认序列化LocalDateTime为嵌套对象,而 fastjson2 可通过JSONWriter.Feature.WriteISO8601Dates控制为标准字符串。
4.2 HikariCP 连接池的 5 个生死参数:调不对,系统凌晨必挂
HikariCP 不是配了就能用,仓库系统高并发下,以下参数决定生死:
| 参数 | 推荐值 | 为什么这么设 | 不设的后果 |
|---|---|---|---|
maximumPoolSize | CPU核数 × 2 + 磁盘数(例:16核2磁盘 → 34) | 仓库 IO 密集,需更多连接应对磁盘等待 | 连接池耗尽,HTTP 500 暴增 |
connection-timeout | 30000(30秒) | 防止个别慢 SQL 拖垮整个池 | 线程阻塞,后续请求排队雪崩 |
idle-timeout | 600000(10分钟) | MySQL 默认wait_timeout=28800(8小时),设太短频繁创建销毁 | 连接抖动,Aborted_connects指标飙升 |
max-lifetime | 1800000(30分钟) | 强制连接定期刷新,规避 MySQL 主从切换后的连接失效 | 主从切换后部分连接持续报Connection reset |
leak-detection-threshold | 60000(60秒) | 检测连接未归还(如 try-with-resources 忘写) | 连接泄漏,ActiveConnections持续增长直至 OOM |
application.yml完整配置:
spring: datasource: hikari: maximum-pool-size: 34 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000 leak-detection-threshold: 60000 # 关键:开启连接泄漏检测日志 metric-registry: com.zaxxer.hikari.metrics.micrometer.MicrometerRegistry注意:
leak-detection-threshold开启后,若某连接超过 60 秒未归还,HikariCP 会打印 WARN 日志并自动回收,这是定位“忘记 close()”问题的后悔药。
4.3 用 Prometheus + Grafana 搭建仓库专属看板:只看 4 个指标
不要一上来就堆 50 个图表。仓库系统只需盯死以下 4 个黄金指标:
| 指标 | PromQL 查询 | 告警阈值 | 业务含义 |
|---|---|---|---|
| 库存负数商品数 | count by (warehouse) (stock_quantity_total{quantity<0}) | > 0 | 立即人工介入,否则影响销售 |
| 单据平均处理时长 | histogram_quantile(0.95, sum(rate(order_process_duration_seconds_bucket[1h])) by (le, type)) | > 5s | 出库单超时,客户投诉源头 |
| MySQL 连接使用率 | 100 * (hikaricp_connections_active{application="warehouse"} / hikaricp_connections_max{application="warehouse"}) | > 90% | 需扩容或查慢 SQL |
| Binlog 延迟秒数 | mysql_slave_seconds_behind_master{job="mysql"} or on() vector(0) | > 60 | 主从不同步,备份不可信 |
Grafana 面板配置要点:
- 所有图表设
Refresh every: 15s,仓库操作需实时感知; - “库存负数”面板用Alert Panel类型,触发时直接邮件+钉钉通知;
- “单据处理时长”用Heatmap,横轴时间、纵轴单据类型、颜色深浅代表 P95 延迟,一眼看出哪类单据最慢。
5. 从开发到上线:生产环境部署与灰度验证的硬核 checklist
5.1 MySQL 8.0.33 生产部署 checklist(Windows/CentOS 通用)
别信“一键安装包”,生产环境必须手动验证以下 7 项:
| 检查项 | 验证命令/方法 | 不通过后果 | 解决方案 |
|---|---|---|---|
| 1. 字符集是否为 utf8mb4 | SHOW VARIABLES LIKE 'character_set%'; | 中文商品名乱码、emoji 报错 | SET NAMES utf8mb4;+ 修改my.cnf的character-set-server=utf8mb4 |
| 2. 时区是否为 Asia/Shanghai | SELECT NOW(); SHOW VARIABLES LIKE 'time_zone'; | 库存流水时间比北京时间晚 8 小时 | default-time-zone='+08:00'加入my.cnf |
| 3. sql_mode 是否禁用 STRICT_TRANS_TABLES | SELECT @@sql_mode; | INSERT INTO stock VALUES (NULL, 'A', 10)不报错但插入 0 | 保留STRICT_TRANS_TABLES,这是数据质量底线 |
| 4. innodb_file_per_table 是否 ON | SHOW VARIABLES LIKE 'innodb_file_per_table'; | 单表过大时无法在线收缩 | SET GLOBAL innodb_file_per_table=ON;(需重启) |
| 5. slow_query_log 是否开启 | SHOW VARIABLES LIKE 'slow_query_log%'; | 慢 SQL 无法定位 | slow_query_log=ON+long_query_time=1 |
| 6. max_allowed_packet 是否 ≥ 64M | SHOW VARIABLES LIKE 'max_allowed_packet'; | 批量导入大单据失败 | max_allowed_packet=67108864(64MB) |
| 7. tmp_table_size 是否 ≥ 256M | SHOW VARIABLES LIKE 'tmp_table_size'; | GROUP BY大结果集写磁盘,慢 10 倍 | tmp_table_size=268435456 |
提示:第 3 项
STRICT_TRANS_TABLES必须开启。曾有项目因关闭它,导致INSERT INTO stock (quantity) VALUES ('abc')插入 0 而不报错,三个月后才发现库存数据大面积污染。
5.2 Java 应用 JVM 参数调优:不是-Xmx4g就完事
仓库系统 GC 压力来自两处:大量短生命周期 DTO 对象(单据解析)、长周期缓存对象(商品信息)。因此必须分代调优:
# 生产启动脚本(Linux) nohup java \ -server \ -Xms4g -Xmx4g \ -XX:+UseG1GC \ -XX:MaxGCPauseMillis=200 \ -XX:G1HeapRegionSize=2M \ -XX:G1ReservePercent=15 \ -XX:+PrintGCDetails \ -Xloggc:/opt/warehouse/logs/gc.log \ -XX:+UseGCLogFileRotation \ -XX:NumberOfGCLogFiles=5 \ -XX:GCLogFileSize=10M \ -jar warehouse.jar > /dev/null 2>&1 &参数详解:
-Xms4g -Xmx4g:堆内存固定,避免动态扩容导致 STW;-XX:+UseG1GC:G1 适合大堆(>4G)且对停顿敏感的场景;-XX:MaxGCPauseMillis=200:G1 目标停顿时间,仓库系统可接受 200ms 暂停;-XX:G1HeapRegionSize=2M:仓库对象大小集中在 100KB~1MB,2M Region 减少跨 Region 引用;-XX:G1ReservePercent=15:预留 15% 堆空间防 Humongous 分配失败(大对象如 1MB JSON);- GC 日志必须开启:
-Xloggc+UseGCLogFileRotation,否则线上 OOM 无法复盘。
5.3 灰度发布 checklist:如何让新版本不惊动仓管员?
仓库系统不能“一刀切”上线。我们采用按仓库 ID 灰度(非按流量),因为业务天然隔离:
| 步骤 | 操作 | 验证方式 |
|---|---|---|
| Step 1:配置中心开关 | Nacos 中新建warehouse.gray.warehouse-ids=WH001,WH002 | 应用启动时读取,仅对 WH001/WH002 生效新逻辑 |
| Step 2:双写日志 | 新版本扣减库存时,同时写老逻辑日志(stock_deduct_old.log)和新逻辑日志(stock_deduct_new.log) | 对比两份日志,确认结果一致 |
| Step 3:数据一致性校验 | 每日凌晨跑脚本:SELECT sku_id, SUM(quantity_change) FROM stock_log WHERE warehouse_id IN ('WH001','WH002') GROUP BY sku_id,与SELECT sku_id, quantity FROM stock比对 | 差异 > 0.1% 则告警 |
| Step 4:全量切换 | 确认 3 天无差异后,将warehouse.gray.warehouse-ids改为* | 监控“库存负数”指标是否突增 |
我的习惯:灰度期间,每天下班前手动执行一次
SELECT COUNT(*) FROM stock_log WHERE create_time > DATE_SUB(NOW(), INTERVAL 1 DAY) AND warehouse_id IN ('WH001','WH002');,确保新逻辑日志量与老逻辑匹配。这是比自动化脚本更可靠的“最后一道眼”。
希望帮到你。
本文还有配套的精品资源,点击获取