窗口函数排序不走索引时会强制磁盘排序,需为PARTITION BY和ORDER BY创建联合降序索引,并将WHERE条件置于窗口函数前以避免全表排序。

窗口函数排序不走索引时会强制磁盘排序
窗口函数的 ORDER BY 不是装饰,而是真实触发排序操作的开关。没索引支撑时,MySQL 8.0+ 或 SQL Server 会把每个分区数据拉进内存排序;一旦超出 sort_buffer_size(MySQL)或 max server memory(SQL Server),就溢出到磁盘临时表——执行计划里会出现 Sort 节点带 Warning: Operator used tempdb to spill data。
- 典型表现:
EXPLAIN中出现Extra: Using filesort,rows值高,Handler_read_next暴增(比如超 10 万次) - 字符串字段要注意校对规则:
utf8mb4_0900_as_cs和utf8mb4_general_ci排序结果不同,可能让优化器放弃索引 - 表达式排序(如
ORDER BY YEAR(order_time))必须建函数索引(MySQL 8.0+ 支持),否则无法避免排序
必须为 PARTITION BY a ORDER BY b 创建联合索引 (a, b)
单列索引没用。窗口函数先按 PARTITION BY 切分,再在每个分区内按 ORDER BY 排序——索引必须能同时支撑这两个动作。
- 顺序不能颠倒:
OVER (PARTITION BY user_id ORDER BY created_time DESC)必须对应INDEX idx_user_created_desc (user_id, created_time DESC) - 降序索引不是可选:MySQL 8.0 才真正支持物理降序存储,
DESC必须显式写在索引定义里,仅靠ORDER BY created_time DESC语句不会自动适配升序索引 - 错误写法:
CREATE INDEX idx_user_created ON orders(user_id, created_time);—— 即使查询写ORDER BY created_time DESC,优化器仍可能拒绝使用该索引做排序
多个窗口函数共用相同 OVER 子句才能复用排序
现代引擎(MySQL 8.0.22+、PostgreSQL 13+、SQL Server 2016+)能识别完全一致的 OVER 子句并复用一次排序结果。但“看似相同实则不同”的写法会失效。
- 安全写法:
ROW_NUMBER() OVER(PARTITION BY dept_id ORDER BY salary DESC)和AVG(salary) OVER(PARTITION BY dept_id ORDER BY salary DESC) - 危险写法:一个写
ORDER BY create_time DESC,另一个写ORDER BY create_time DESC ASC(语法合法但语义冗余,部分版本仍分别排序) - 验证方式:
EXPLAIN中只应出现一个Sort或Window Spool节点,而不是每个函数都带一个
WHERE 提前过滤比在 CTE 里套窗口更省资源
窗口函数无法下推过滤条件。写 WITH ranked AS (SELECT *, ROW_NUMBER() OVER() rn FROM orders) 再 WHERE rn <= 10,引擎仍会先算完整个表的 row number——如果原始表有千万行,而你只关心最近 7 天的 2 万单,就白干了 99.8% 的排序工作。
- 永远优先把
WHERE放在窗口之前:SELECT *, ROW_NUMBER() OVER() FROM orders WHERE order_time >= '2026-07-14' - JOIN 后排序更危险:关联放大结果集,若排序字段不在驱动表上,又没覆盖索引,极易触发磁盘排序
- 必要时把排序逻辑下推到子查询:
SELECT * FROM (SELECT * FROM a ORDER BY updated_at DESC LIMIT 1000) a_sub JOIN b ON ...,让外层窗口只处理 1000 行
实际执行时,最容易被忽略的是索引定义中 DESC 的显式声明和 WHERE 位置——这两处一错,其他优化全白搭。

















