窗口函数慢的关键是over子句中order by字段未走索引,必须为partition by+order by字段建立前缀匹配的复合索引,且desc需显式定义在索引中。

窗口函数为什么慢?关键在 ORDER BY 没走索引
窗口函数本身不慢,慢的是 OVER (PARTITION BY ... ORDER BY ...) 里那个 ORDER BY 字段没索引。MySQL 8.0 执行窗口函数时,会先按 PARTITION BY 分组,再在每个分组内按 ORDER BY 排序——如果排序字段没索引,就得对每个分组做内存排序(Using filesort),数据量一大就卡住。
典型表现:EXPLAIN 中出现 Extra: Using filesort,rows 值高,Handler_read_next 暴增(比如超 10 万次)。
- 必须确保
PARTITION BY+ORDER BY的字段组合有**前缀匹配的复合索引** - 顺序不能错:索引定义要和
OVER子句中字段顺序完全一致,例如OVER (PARTITION BY user_id ORDER BY create_time DESC)就得建INDEX idx_user_time (user_id, create_time DESC) - 别用单列索引:只给
create_time加索引没用,因为窗口函数需要先按user_id切分,再各自排序
降序索引不是可选,而是刚需
MySQL 8.0 之前,ORDER BY col DESC 实际走的是升序索引反向扫描,性能差且无法避免 filesort;8.0 真正支持物理降序存储,DESC 索引能直接跳过排序步骤。
错误写法:CREATE INDEX idx_user_created ON orders(user_id, created_time); —— 即使查询是 ORDER BY created_time DESC,优化器仍可能拒绝使用该索引做排序。
- 正确建法:
CREATE INDEX idx_user_created_desc ON orders(user_id, created_time DESC); - 验证是否生效:
EXPLAIN SELECT ..., ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_time DESC) FROM orders;看Extra是否还有Using filesort - 注意:
DESC必须显式写在索引定义里,不能靠ORDER BY语句里写DESC就自动适配
覆盖索引能省掉回表,但别强求
窗口函数执行时,MySQL 需要读取所有参与计算的列。如果 SELECT 的字段全在索引里(即覆盖索引),就能避免回主键查找,减少 I/O。
但优先级低于排序索引:先保证 PARTITION BY + ORDER BY 走索引,再考虑把常用 SELECT 列加进去。
- 例如查询:
SELECT user_id, order_time, amount, SUM(amount) OVER (PARTITION BY user_id ORDER BY order_time) FROM orders; - 基础索引:
INDEX idx_user_time (user_id, order_time) - 进阶覆盖索引:
INDEX idx_user_time_amt (user_id, order_time, amount)—— 注意amount放最后,不影响最左前缀匹配 - 别把大字段(如
TEXT、长VARCHAR)塞进索引,会拖慢写入和增大索引体积
生成列索引适合 JSON 或表达式场景
当 ORDER BY 或 PARTITION BY 依赖 JSON 提取或函数结果(如 DATE(created_at)),普通索引无效,必须用生成列 + 索引。
常见陷阱:直接写 ORDER BY JSON_EXTRACT(data, '$.score'),哪怕 data 有索引也完全不走。
- 先加生成列:
ALTER TABLE results ADD COLUMN score_gen INT GENERATED ALWAYS AS (JSON_EXTRACT(data, '$.score')) STORED; - 再建索引:
CREATE INDEX idx_score_gen ON results(score_gen); - 改写查询:
ROW_NUMBER() OVER (ORDER BY score_gen DESC) - 虚拟列(
STORED)才支持索引;VIRTUAL列不行
真正卡住窗口函数性能的,从来不是函数本身,而是底层数据访问路径没对齐。索引建不对,ROW_NUMBER() 和 SUM() OVER () 都会退化成 N² 级别扫描。重点盯死 EXPLAIN 里的 type 和 Extra,别的都是次要的。











