mysql 5.7 存储过程无法直接使用窗口函数实现滑动平均,需通过临时表、游标和循环手动模拟;关键在于索引优化、时间边界处理、避免全表扫描及及时清理临时表。

MySQL 5.7 存储过程里没法直接用窗口函数做滑动平均
MySQL 5.7 不支持 LAG()、AVG() OVER () 这类窗口函数,也没法用 WITH RECURSIVE 构造时间轴或递归计算。所谓“平滑”,比如 5 分钟滑动平均、7 日移动均值,必须靠存储过程手动模拟——不是调个函数的事,而是得自己建临时表、按时间窗口逐行扫描、累加再除数。
用临时表 + 游标 + 循环实现滑动窗口聚合
核心思路是:先生成目标时间点(如每分钟一个),再对每个点查出其前 N 个时间单位内的原始数据,算均值。关键不是“能不能写”,而是避免全表扫、索引失效和循环失控:
-
tmp_points表必须带PRIMARY KEY(dt)或INDEX(dt),否则 JOIN 时性能断崖式下跌 - 别在游标循环里写
SELECT AVG(x) FROM t WHERE time BETWEEN ...—— 每次都触发全表扫描;应提前把原始数据按time排序并加INDEX(time),再用time >= ? AND time 范围查询 - 游标声明必须加
DECLARE cur CURSOR FOR SELECT dt FROM tmp_points ORDER BY dt;,漏掉ORDER BY会导致窗口错位 - 每次计算完记得
INSERT INTO result_table (dt, smoothed_value) VALUES (cur_dt, @avg_val);,别用REPLACE INTO,它会触发唯一键校验,慢 3–5 倍
DATE_SUB 和 UNIX_TIMESTAMP 的边界陷阱
做时间偏移时,DATE_SUB(cur_dt, INTERVAL 5 MINUTE) 看似简单,但容易踩坑:
- 如果
cur_dt是VARCHAR或没显式转成DATETIME,DATE_SUB可能静默返回NULL,后续所有计算崩掉 - 用
UNIX_TIMESTAMP()做数值运算(比如ts - 300)比DATE_SUB更稳定,但要注意时区:确保 session 时区和数据存储时区一致,否则差 8 小时 - 边界判断必须显式:例如
IF @start_ts ,不然窗口越界就查不到数据
性能卡点往往在索引和临时表生命周期
实际压测发现,90% 的慢查询不是逻辑错,而是索引没生效或临时表没清理:
-
raw_data.time字段必须是DATETIME或TIMESTAMP类型,并建INDEX(time);如果存的是字符串(如'2024-01-01 12:00:00'),哪怕加了索引也无效 - 临时表
tmp_points建完立刻ANALYZE TABLE tmp_points;,否则优化器可能误判行数,选错 JOIN 方式 - 存储过程末尾务必
DROP TEMPORARY TABLE IF EXISTS tmp_points;,不删会导致下一次调用时表已存在报错,且残留数据污染结果 - 超过 1 万行的平滑计算,建议分批处理(如每次 1000 行),用
WHERE dt BETWEEN ? AND ?控制范围,避免单次事务过大锁表
真正难的不是写出能跑的代码,而是让每一次 DATE_ADD() 都落在索引上、每一次 JOIN 都走 ref 而非 ALL、每一次循环都清楚自己在哪个时间点——这些细节不盯住,平滑就变成卡顿。











