lag和lead是逐行计算的窗口函数,每行返回一个标量值(当前行上下第n行对应字段值),不改变结果集行数;必须配合order by使用,推荐显式指定default_value以防null引发计算错误。

LAG 和 LEAD 不是“获取前N行数据”,而是取相邻行的值
很多人看到 LAG() 就以为它能像 LIMIT 那样返回多行结果——不是的。LAG() 和 LEAD() 是**逐行计算型窗口函数**,每调用一次只返回一个标量值(当前行的上/下第 N 行对应字段的值),不会改变结果集行数。想“取前N行记录”该用 ORDER BY ... LIMIT N;而这里的目标其实是:**在每一行上,快速拿到它前后偏移位置上的某个字段值**。
正确写法:必须带 ORDER BY,推荐显式指定 default_value
这两个函数语法看似简单,但漏掉关键子句就会出问题:
-
ORDER BY是强制要求的——没有排序就没有“前后”概念,MySQL 会直接报错ERROR 3589 (HY000): Window '<name>' requires an ORDER BY clause</name> - 不设
default_value时,首行调用LAG(col, 1)返回NULL,末行调用LEAD(col, 1)也返回NULL;如果业务逻辑不允许空值(比如做减法或除法),得补上第三参数,例如LAG(amount, 1, 0) - 偏移量
offset必须是非负整数,MySQL 8.0.22+ 要求必须明确写出(不能省略),且范围是 1 到 2^63−1
按业务维度分组计算:用 PARTITION BY 隔离逻辑边界
比如分析每个产品的日销售额环比,就不能让 A 产品最后一天和 B 产品第一天连起来算——必须按产品切分窗口:
SELECT product_id, sale_date, amount, LAG(amount, 1, 0) OVER (PARTITION BY product_id ORDER BY sale_date) AS prev_day_amount, amount - LAG(amount, 1, 0) OVER (PARTITION BY product_id ORDER BY sale_date) AS daily_diff FROM sales;
注意点:
-
PARTITION BY后的字段必须是查询中出现的列(或其表达式),否则报错Unknown column - 若同时对多个字段分组(如
PARTITION BY category_id, region),分区粒度变细,各组合内独立排序计算 - 不要在
PARTITION BY中混用无序字段(如未索引的时间戳字段),否则排序开销剧增
性能陷阱:ORDER BY 字段没索引,查询可能慢十倍
窗口函数执行前,MySQL 必须先完成全量排序。如果 ORDER BY 字段没索引,尤其是大表,会触发 Using filesort,IO 拉满:
- 用
EXPLAIN检查执行计划,看到Extra: Using filesort就要警惕 - 给常用排序字段建索引,例如
CREATE INDEX idx_sale_date ON sales(sale_date)或复合索引CREATE INDEX idx_prod_date ON sales(product_id, sale_date) - 避免在子查询里嵌套窗口函数再加
WHERE过滤——旧版本 MySQL 会先算完全部窗口再过滤,浪费资源 - 确认 MySQL 版本 ≥ 8.0.2,低于这个版本直接不支持,报错
ERROR 1064
最常被忽略的其实是 ORDER BY 的存在本身,以及默认值缺失导致后续计算崩掉——尤其当差值用于报表展示或告警阈值判断时,NULL 可能悄无声息地让整个指标失效。











