erp系统常见优化点

反例:循环里一次次改库存

退货时,订单里有几种商品,就在代码里循环几种商品,每循环一次就向数据库发一条「把某个商品的库存加回去」的更新。

复制代码
退货 3 种商品
→ 更新库存(商品 A)
→ 更新库存(商品 B)
→ 更新库存(商品 C)
→ 共 3 次数据库往返

看起来直观,但有几个问题:

  • 商品一多,数据库往返次数线性上涨
  • 同一商品若出现多行,还可能被更新多次,既慢又容易算错

正例:先汇总,再一条语句批量更新

先把「要退的商品 + 数量」在内存里整理成一张小清单(同一商品合并数量),再交给数据库 一次更新:用分支表达式按商品编号决定每一行加多少。

复制代码
退货明细
  A × 2
  B × 5
  A × 1
      ↓ 合并
清单:A→3,B→5
      ↓
一条更新:
  仓里的 A 库存 +3
  仓里的 B 库存 +5

伪代码大意:

复制代码
1. 遍历退货明细,得到 Map:商品 → 总数量
2. 转成列表:[{商品, 数量}, ...]
3. 执行一次批量 UPDATE:
   库存 = 库存 +(按商品编号分支:A 加 3,B 加 5,...)
   且只更新列表里出现的那些商品

好处

说明
更少往返 N 次更新变成 1 次,延迟和连接开销更低
语义更清晰 「这一单要回多少货」先算清,再落库,业务和写库分层
避免重复更新 同商品多行先合并,库存只加一次正确总量
更好维护 以后换批量策略(分批、消息队列)只动落库这一层

一句话:不要在循环里打数据库,先把变更清单算好,再让数据库一次按清单改到位。

代码

优化前

java 复制代码
// 退货回补库存 ------ 优化前:N 次 SQL
for (OrderLine line : refundLines) {
    if (line.getSkuCode() == null) {
        continue;
    }
    BigDecimal qty = line.getQty() != null
            ? line.getQty().abs()
            : BigDecimal.ZERO;
    if (qty.compareTo(BigDecimal.ZERO) <= 0) {
        continue;
    }
    // 每次循环打一次库
    stockService.increase(warehouseId, line.getSkuCode(), qty.intValue());
}

对应 Mapper:

xml 复制代码
<!-- 单条加库存 -->
<update id="increase">
    UPDATE warehouse_stock
    SET qty = qty + #{qty}
    WHERE warehouse_id = #{warehouseId}
      AND sku_code = #{skuCode}
</update>

优化后:聚合 + 一条批量 SQL

java 复制代码
// 退货回补库存 ------ 优化后:先聚合,再批量 1 条 SQL
Map<String, BigDecimal> qtyBySku = new HashMap<>();
for (OrderLine line : refundLines) {
    if (line.getSkuCode() == null) {
        continue;
    }
    BigDecimal qty = line.getQty() != null
            ? line.getQty().abs()
            : BigDecimal.ZERO;
    if (qty.compareTo(BigDecimal.ZERO) <= 0) {
        continue;
    }
    // 同一 SKU 合并,避免重复更新
    qtyBySku.merge(line.getSkuCode(), qty, BigDecimal::add);
}

if (!qtyBySku.isEmpty()) {
    List<StockChangeItem> items = new ArrayList<>();
    for (Map.Entry<String, BigDecimal> e : qtyBySku.entrySet()) {
        items.add(new StockChangeItem(e.getKey(), e.getValue().intValue()));
    }
    stockService.batchIncrease(warehouseId, items);
}

DTO:

java 复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class StockChangeItem {
    private String skuCode;
    private Integer qty;
}

批量 SQL(核心):

xml 复制代码
<!-- 批量加库存:一条 UPDATE + CASE WHEN -->
<update id="batchIncrease">
    UPDATE warehouse_stock
    SET qty = qty + CASE sku_code
    <foreach collection="items" item="it">
        WHEN #{it.skuCode} THEN #{it.qty}
    </foreach>
        ELSE 0
    END
    WHERE warehouse_id = #{warehouseId}
      AND sku_code IN
    <foreach collection="items" item="it" open="(" separator="," close=")">
        #{it.skuCode}
    </foreach>
</update>
sql 复制代码
UPDATE warehouse_stock
SET qty = qty + CASE sku_code
    WHEN 'SKU001' THEN 2
    WHEN 'SKU002' THEN 5
    ELSE 0
END
WHERE warehouse_id = 'WH01'
  AND sku_code IN ('SKU001', 'SKU002');
相关推荐
吴声子夜歌1 小时前
Redis 3.x——集群运维
运维·数据库·redis
钝挫力PROGRAMER1 小时前
MySQL 数据截断错误 #22001:原因分析与解决方案
数据库·mysql
Database_Cool_2 小时前
记忆张量MemOS + 阿里云PolarDB一站式记忆管理方案发布:给AI装上不断片的记忆
数据库·人工智能·阿里云
码农阿豪2 小时前
上线前能跑,上线后挂了,三个隐性的SQL陷阱
数据库·sql
春卷同学2 小时前
032-关系型数据库在记账应用中的建模与查询优化
java·jvm·数据库
foolishlee2 小时前
openGauss慢SQL记录相关线程
数据库
乖巧的妹子2 小时前
SQL知识点
数据库·sql·oracle
数行拙笔2 小时前
Redis---list类型
数据库·redis·缓存
人工智能培训4 小时前
世界模型内嵌,感知交互跃迁
大数据·数据库·人工智能·神经网络·生成对抗网络