关联子查询计算移动平均性能极差,因其每行触发一次独立子查询执行,10万行即执行10万次,且每次需重新扫描排序子集;必须显式ORDER BY timestamp定义行序,MySQL 5.7还不支持子查询中用外层变量控制LIMIT。

关联子查询能算移动平均,但性能极差,别在大表上直接用。
为什么 WHERE 中的关联子查询会拖慢移动平均计算
移动平均需要对每个行取「当前行及前N行」的数据求均值,而关联子查询在 MySQL 或 PostgreSQL 中每行都会触发一次独立子查询执行。例如按 category 分组、窗口为 3 行的移动平均,若表有 10 万行,就可能执行 10 万次子查询,且每次都要重新扫描排序后的子集。
- 子查询里必须显式写
ORDER BY timestamp才能定义“前几行”,否则顺序无保证 - MySQL 5.7 不支持
LIMIT在子查询中引用外层变量(如WHERE t2.timestamp ),8.0+ 才勉强可行 - PostgreSQL 允许,但
SELECT AVG(value) FROM t2 WHERE t2.category = t1.category AND t2.ts 这类写法实际无法保证取到“连续时间上前3条”,因为LIMIT在WHERE之后生效,排序和截断逻辑混乱
替代方案:用 AVG() OVER() 窗口函数重写(推荐)
所有主流数据库(PostgreSQL、SQL Server、Oracle、MySQL 8.0+、SQLite 3.25+)都支持标准窗口函数,语法清晰、语义明确、执行计划可优化。
- 正确写法是:
AVG(value) OVER (PARTITION BY category ORDER BY ts ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) -
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW明确指定“包含当前行及往前2行”,共3个点,不是时间范围 - 如果想按时间范围(如“过去7天”),得用
RANGE BETWEEN INTERVAL '6 days' PRECEDING AND CURRENT ROW(仅 PostgreSQL / Oracle 支持;MySQL 不支持RANGE+INTERVAL) - 注意
ts列必须为TIMESTAMP或DATE类型,且含索引,否则ORDER BY窗口仍可能慢
兼容老版本 MySQL(5.7 及更早)的兜底做法
没有窗口函数,又不能升级,只能靠自连接 + 变量模拟,但必须满足两个前提:数据已按 category, ts 排好序,且每组内 ts 严格递增无重复。
- 先用
JOIN关联同分类下ts 的记录,再用变量给每组内行编号:@rn := IF(@cat = category, @rn + 1, 1) - 外层过滤
rn - rank (即只取最近3行),再GROUP BY id, category, ts求均值 - 这种写法极易出错:变量赋值顺序依赖执行计划,MySQL 5.7 优化器可能重排,导致编号错乱;一旦
ts有重复,JOIN会爆炸性膨胀 - 真要硬上,务必加
FORCE INDEX强制走(category, ts)联合索引,否则JOIN成全表扫描
真正麻烦的从来不是“怎么写出能跑的 SQL”,而是“怎么让移动平均在 100 万行数据上稳定在 200ms 内返回”。窗口函数是唯一靠谱路径;任何用关联子查询或变量模拟的方案,上线后第一个流量高峰就会暴露问题。











