必须显式写ORDER BY,否则SUM() OVER(PARTITION BY user_id)返回每组总和而非逐行累加;默认窗口帧为UNBOUNDED PRECEDING TO UNBOUNDED FOLLOWING,无排序则无法定义“当前行之前”范围;正确写法需ORDER BY created_at, id等确定性排序,并建(user_id, created_at)联合索引优化性能。

SUM() OVER() 在 MySQL 8.0 中必须显式写 ORDER BY,否则不是累计求和,而是分组总和。
为什么 SUM(amount) OVER(PARTITION BY user_id) 不是运行总计
这个写法语法合法,但结果不符合“逐行累加”预期:它对每个 user_id 分组返回**固定值**(即该用户全部 amount 的总和),所有同组行的累计列都一样。真正需要的是按时间、ID 等顺序一行一行加起来的值。
根本原因在于窗口帧默认是 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING,也就是整个分组范围——没指定排序,就无法定义“当前行之前有哪些行”,自然没法做运行总计。
- 正确写法必须带
ORDER BY:例如SUM(amount) OVER(PARTITION BY user_id ORDER BY created_at) - 如果
created_at有重复,建议补二级排序,如ORDER BY created_at, id,避免非确定性结果 - MySQL 不支持
NULLS FIRST,若排序字段可能为 NULL,先用COALESCE(created_at, '1970-01-01')统一处理
大数据量下 cumulative_total 越查越慢?检查执行计划里的 Using filesort
窗口函数依赖排序,而排序成本随数据量陡增。当 EXPLAIN 输出中出现 Using filesort,说明 MySQL 正在内存或磁盘上临时排序,10 万行以上延迟明显。
优化方式非常直接:给 PARTITION BY 和 ORDER BY 字段建联合索引,且顺序必须一致。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 例如用
PARTITION BY user_id ORDER BY created_at,就建:CREATE INDEX idx_user_created ON orders (user_id, created_at) - 反过来建
(created_at, user_id)无效——索引最左前缀不匹配窗口定义顺序 - 如果查询还带了
WHERE user_id IN (…),这个索引依然能覆盖,但若 WHERE 条件是created_at > '2025-01-01',则需评估是否要调整索引策略
想过滤累计值大于 100 的记录?不能在 WHERE 里直接写 running_sum > 100
窗口函数不能出现在 WHERE 或 HAVING 子句中,这是 SQL 标准限制,不是 MySQL 特有。写成 WHERE SUM(amount) OVER(...) > 100 会直接报错:ERROR 3579 (HY000): Window function is not allowed in WHERE。
必须把窗口计算放在子查询或 CTE 中,再对外层结果过滤:
WITH t_with_running AS (
SELECT *,
SUM(amount) OVER(PARTITION BY user_id ORDER BY created_at) AS running_sum
FROM orders
)
SELECT * FROM t_with_running WHERE running_sum > 100;- 别用
SELECT *, SUM(...) OVER() AS s FROM t WHERE s > 100——语法错误,连解析都过不去 - CTE 比嵌套子查询更易读,尤其当有多重窗口计算时
- 注意:CTE 中的窗口计算仍受索引影响,该优化的索引一个都不能少
同一分组内多行时间相同,累计值却每次不一样?排序不稳定是元凶
当 ORDER BY created_at 遇到重复值,MySQL 无法保证这些行之间的相对顺序,导致不同执行中窗口包含的“前面几行”不一致,最终 running_sum 值跳变。报表对账时数字对不上,大概率卡在这里。
这不是 bug,是未定义行为。修复的关键是让排序**完全确定**:
- 强制加上唯一字段:如
ORDER BY created_at, id(假设id是主键) - 避免用函数包裹排序字段,例如
ORDER BY DATE(created_at)会丢失精度且无法走索引 - 如果业务允许,可提前在写入时保证
created_at微秒级唯一(如用NOW(6))
最常被忽略的,其实是分区边界和排序稳定性的组合影响——哪怕索引建对了、语法写对了,只要排序键不唯一,结果就不可重现。

















