因为sum() over是窗口函数,不压缩行数,只为每行计算累计值;而库存累计需按时间逐笔更新当前状态,必须确保排序稳定(如order by time, id)、期初库存纳入、出入库符号统一,并过滤无效记录,否则结果不可靠。

为什么 SUM OVER 不能直接替代 GROUP BY 做库存累计?
因为 SUM() OVER 是窗口函数,它不压缩行数,而是为每一行计算一个累计值;而进销存核算真正需要的是“按时间顺序逐笔更新当前库存”,不是单纯求和。如果原始数据里存在同一时间点的多条出入库记录(比如同秒入库 3 批货),SUM() OVER (ORDER BY timestamp) 的结果取决于排序稳定性——MySQL 8.0+ 默认不保证同序键下的物理顺序,可能造成相同时间戳的记录每次执行结果不一致。
- 必须显式添加唯一排序依据,例如:
ORDER BY timestamp, id或ORDER BY timestamp, transaction_type DESC(出库优先于入库可避免中间负库存误判) - 原始数据中
quantity字段需统一符号:入库为正、出库为负,否则累计逻辑会错乱 - 若表中存在未提交事务或软删除标记(如
is_deleted = 1),必须在WHERE子句中提前过滤,否则窗口函数也会把无效记录纳入计算
如何用 SUM OVER 计算每笔业务后的实时库存?
核心是构造一个带确定性排序的窗口,并确保起始库存被正确纳入。常见做法是把期初库存作为首条虚拟记录插入,再统一累加:
SELECT id, transaction_type, quantity, SUM(quantity) OVER (ORDER BY effective_time, id ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS current_stock FROM ( SELECT id, transaction_type, quantity, effective_time FROM stock_log WHERE status = 'confirmed' UNION ALL SELECT NULL AS id, 'initial' AS transaction_type, 100 AS quantity, '2024-01-01 00:00:00' AS effective_time ) t ORDER BY effective_time, id;
-
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW比默认范围更明确,避免某些旧版本 MySQL 对 RANGE 的隐式行为误解 - 虚拟期初记录的
effective_time必须早于所有真实业务时间,且id设为NULL可确保它排在最前(多数数据库对NULL在ORDER BY中默认排最前) - 若需按商品维度独立核算,必须加上
PARTITION BY item_id,且PARTITION BY一定要放在ORDER BY前面,否则会跨商品混算
遇到“窗口函数结果跳变”该怎么排查?
典型现象是某笔记录的 current_stock 突然比上一笔少很多,甚至出现负值,但单看该行 quantity 并不大。这通常不是 SQL 写错了,而是数据本身有问题:
- 检查是否存在重复
id或未去重的同步数据——同一笔业务被插入两次,SUM OVER会累加两遍 - 确认
effective_time是否有跨天/时区问题,例如数据库用 UTC 时间但业务系统传的是本地时间,导致排序错乱 - 留意
quantity是否被错误地存储为字符串(如'-5'),部分数据库会静默转成 0,使累计值偏高 - 使用
LAG(current_stock) OVER (...)和quantity做差值校验,快速定位哪一行的累计偏离了预期增量
性能差到查不动?别硬扛,先拆解瓶颈
当 stock_log 表超过百万行,SUM() OVER 全表扫描 + 排序很容易拖慢查询。优化方向很实际:
- 给
(item_id, effective_time, id)建联合索引——这是PARTITION BY + ORDER BY最高效的驱动路径 - 避免在大表上直接
SELECT * ... SUM() OVER,先用子查询或 CTE 过滤出目标item_id和时间范围,再开窗 - 如果只是查最新库存,不要用窗口函数,改用
(SELECT SUM(quantity) FROM stock_log WHERE item_id = ? AND effective_time 配合覆盖索引,速度往往更快 - 历史报表类需求,建议物化累计结果到汇总表,用触发器或定时任务维护,而不是每次实时计算
窗口函数很适合表达逻辑,但库存核算是个状态累积过程,真实系统里边界条件(如退货冲回、盘点调整、批次效期)远比 SQL 能直接描述的复杂。别指望一条 SUM OVER 吃下所有场景,关键路径上留好人工复核入口更稳妥。











