MySQL 5.7及更早版本需用用户变量模拟运行总计,必须先在子查询中显式排序并初始化变量,再在外层累加,避免ORDER BY与变量执行顺序不同步导致结果错误。

MySQL 5.7 及更早版本怎么写 running total
MySQL 5.7 不支持 OVER() 和窗口函数,必须用用户变量(@var)模拟。核心思路是:按业务顺序排序后,逐行累加,把上一行的累计值“传”到下一行。
常见错误是忽略 ORDER BY 在变量赋值中的执行顺序——MySQL 不保证 SELECT 中变量计算与 ORDER BY 的同步性,必须把排序逻辑显式塞进子查询或派生表里。
- 必须用子查询先排序,再在外层用变量累加,不能直接
SELECT @sum := @sum + amount FROM t ORDER BY id - 初始化变量要和查询放在同一语句中(用
CROSS JOIN或(SELECT @sum := 0) AS _),避免会话残留值干扰 - 字段别名不能和变量名同名(比如
@sum和列也叫sum),否则可能引发不可预期覆盖
SELECT id, amount, @sum := @sum + amount AS running_total FROM orders CROSS JOIN (SELECT @sum := 0) AS _ ORDER BY created_at, id;
为什么不能在 GROUP BY 后直接用变量算 running total
因为 GROUP BY 会压缩行,而变量是在结果集的每一“输出行”上执行的。如果你先 GROUP BY day 再想算每日累计,变量看到的是聚合后的行,但缺少原始时间序——它不知道“昨天”和“今天”的先后关系。
正确做法是:先按时间展开明细(哪怕只是虚拟的计数行),或用自连接/相关子查询;变量方案只适用于已严格排序的线性序列。
- 变量方案本质是“流式处理”,依赖物理返回顺序,不适用于多维度分组累计
- 如果真需要分组内 running total(如每个 user_id 下按时间累计),必须先
ORDER BY user_id, created_at,且确保 user_id 是排序第一优先级 - MySQL 8.0+ 应直接用
SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at),别硬套变量
变量方案在 LIMIT 或分页时为什么结果错乱
因为 LIMIT 是在变量计算**之后**才截断结果的。例如你写 SELECT ..., @sum := @sum + x LIMIT 10,变量其实已经对全表所有行执行了累加,只是最后只显示前 10 行——running total 值完全不对。
解决办法只有一个:把 LIMIT 放进内层排序子查询,让变量只作用于你要的那批数据。
SELECT id, amount, @sum := @sum + amount AS running_total FROM ( SELECT id, amount FROM orders ORDER BY created_at LIMIT 20 ) AS limited CROSS JOIN (SELECT @sum := 0) AS _;
MySQL 8.0 用窗口函数更稳,但要注意 partition 边界
虽然不用变量了,但 SUM() OVER (ORDER BY ...) 默认是“从分区首行到当前行”,不是“从表头到当前行”。如果漏写 PARTITION BY,而数据本身跨多业务主体(比如混着多个 user_id),就会出现跨用户累计——这是比变量还隐蔽的逻辑错误。
- 确认是否需要分组累计:需要就写
PARTITION BY user_id ORDER BY created_at - 不需要分组但数据量大,考虑加索引在
(created_at)或(user_id, created_at)上,否则OVER排序成本高 - MySQL 8.0.2+ 支持
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,显式声明范围更安全,避免未来版本语义变更影响
变量模拟终究是妥协方案,真正上线系统里,只要能升级 MySQL 版本,就别在 production 里靠 @sum 算核心报表的 running total——顺序依赖太脆弱,连 explain 都看不出问题在哪。










