直接用 GROUP BY 算不出实时库存流水,因为库存是随时间推移动态变化的状态量,需按时间顺序逐行累加净变动(入库为正、出库为负),而非静态聚合;必须用 SUM() OVER (PARTITION BY sku_id ORDER BY created_at, id) 实现分区有序累计,且依赖正确索引保障性能。

为什么直接用 GROUP BY 算不出实时库存流水
因为库存是随时间推移不断变化的“状态量”,不是静态聚合值。你有一张 inventory_log 表,记录每次出入库操作(op_type 为 'in' 或 'out'),每条记录带 amount 和 created_at。如果只写 SELECT SUM(amount) FROM inventory_log,得到的是总余额,不是每一笔之后的“当前库存”。关键在于:你需要按时间顺序逐行累加,而不是一次性聚合。
SUM OVER 需要严格指定 ORDER BY 才能动态累计
SUM(amount) OVER (ORDER BY created_at, id) 是核心写法。这里有两个易错点:
-
ORDER BY必须包含能唯一确定处理顺序的列 —— 单靠created_at可能有重复(尤其毫秒级精度不足或批量插入时),务必补上id或其他唯一递增字段作为第二排序条件 - 不能写成
SUM(amount) OVER ()(空窗口),那等价于全表求和,失去“逐行累计”意义 - 如果业务要求区分仓库或商品,必须加
PARTITION BY warehouse_id, sku_id,否则不同 SKU 的流水会混在一起累加
示例语句:
SELECT
id,
sku_id,
op_type,
amount,
SUM(CASE WHEN op_type = 'in' THEN amount ELSE -amount END)
OVER (PARTITION BY sku_id ORDER BY created_at, id) AS current_stock
FROM inventory_log;
出入库金额需统一为正负数再累计
别直接对原始 amount 做 SUM OVER。多数系统里 in 和 out 的 amount 都存正值,所以得用 CASE 转换符号。否则 out 会被当成正向增加,结果全错。
- 常见错误写法:
SUM(amount) OVER (...)—— 忽略业务含义,把出库当入库加 - 正确做法:用
CASE WHEN op_type = 'in' THEN amount ELSE -amount END构造净变动值 - 如果表中已有
delta字段(已含正负),可直接用SUM(delta) OVER (...)
性能隐患:ORDER BY 字段没索引会很慢
当 inventory_log 表数据量超过十万行,SUM OVER (ORDER BY created_at, id) 查询可能明显变慢 —— 因为窗口函数依赖排序,而排序没索引支撑时要走文件排序(Using filesort)。
- 必须在
(sku_id, created_at, id)上建联合索引(顺序不能乱,PARTITION BY字段放最前) - 如果查询常按时间范围过滤(如最近7天),索引可改为
(sku_id, created_at, id),让 WHERE 条件也能命中索引 - MySQL 8.0+ 和 PostgreSQL 支持该写法;但 MySQL 5.7 不支持窗口函数,强行用会报错
ERROR 1064: You have an error in your SQL syntax
实时库存流水本质是状态追踪,不是快照计算。只要顺序、符号、分区三者任一出错,累计值就会从某一行开始持续偏差,而且越往后错得越离谱 —— 这种错误很难被肉眼发现,只能靠核对关键节点的手工台账来暴露。










