窗口函数推荐替代自连接查询,因其单次扫描即可完成排序标号(如ROW_NUMBER())、跨行引用(LAG/LEAD)和累计求和(SUM() OVER),避免多次关联或子查询导致的全表嵌套、I/O暴增与结果不稳定。

因为窗口函数能单次扫描完成原本需要多次关联或子查询的逻辑,执行更快、SQL更短、结果更稳。
ROW_NUMBER() OVER + 子查询过滤比自连接查“最新记录”快得多
自连接查每个用户的最新订单,常写成 NOT EXISTS 或 JOIN 匹配更大时间戳,数据一过十万行,执行计划就容易走全表嵌套,EXPLAIN 里看到 Type: ALL 和 Rows 爆涨是常态。
- 窗口函数直接在分区内排序标号,再外层过滤:
rn = 1,MySQL 只需一次排序+一次扫描 - 必须带
PARTITION BY user_id,否则变成全表排号,失去业务意义 -
ORDER BY created_at DESC, id DESC是关键:时间相同时靠id保序,避免因排序不稳定导致每次取到不同记录 - 如果业务允许并列(比如同一秒两条订单都算“最新”),就不能用
ROW_NUMBER(),得换RANK()或加去重逻辑
LAG()/LEAD() 替代 “按ID自增关联” 更可靠
有人用 JOIN t1 ON t2.id = t1.id + 1 查前后行差值,但生产环境 ID 绝对不连续——删过数据、批量插入、分布式主键都会让这个假设崩掉。
-
LAG(value) OVER (PARTITION BY user_id ORDER BY event_time)才是正解:它不依赖物理存储顺序,只认ORDER BY定义的逻辑顺序 -
LAG(value, 1, 0)第三个参数是默认值,前一行不存在时填0,避免NULL污染后续计算(比如做减法得NULL) - 如果
event_time有重复,且你又没在ORDER BY里加二级排序字段(如id),MySQL 8.0 可能每次返回不同“前一行”,结果不可复现 -
LEAD()同理,只是方向相反;两者都不能脱离ORDER BY单独存在,否则报错ERROR 3589 (HY000): Window '<unnamed>' requires an ORDER BY clause</unnamed>
SUM() OVER 替代关联子查询做累计求和,ROWS 比 RANGE 更准
传统写法如 (SELECT SUM(amount) FROM orders o2 WHERE o2.date <= o1.date),每行触发一次子查询,N 行就是 N 次扫描,I/O 压力指数级上升。
- 用
SUM(amount) OVER (ORDER BY date ROWS UNBOUNDED PRECEDING),MySQL 内部用累积算法单趟搞定 - 必须显式写
ROWS,别省略——RANGE在时间字段上会把相同值的多行全纳入,导致“同一天多笔订单”被重复累加,语义错误 -
ROWS UNBOUNDED PRECEDING表示从分区开头到当前行,严格按行序;而RANGE UNBOUNDED PRECEDING是按值范围,隐含“所有等于当前date的行”,极易踩坑 - 如果日期字段含
NULL,MySQL 8.0 默认把NULL排最前,可能让第一行的累计值异常
真正容易被忽略的是:窗口函数不是万能的,PARTITION BY 和 ORDER BY 的组合必须和业务语义完全对齐,少一个字段、顺序反了、漏了唯一性保障,结果就可能错得悄无声息。


















