partition by字段必须建索引,因为row_number()需先按该字段分组切块再排序编号;无索引将触发全表扫描、哈希分组或磁盘临时表,而联合索引(分区字段最左+排序字段紧随)可使物理数据天然有序,跳过切块阶段,显著提升性能。

为什么PARTITION BY字段必须建索引
因为ROW_NUMBER()在执行时,数据库要先按PARTITION BY字段把数据“切块”,再在每块内排序编号。如果这个字段没索引,就得全表扫描+哈希分组或排序,临时写磁盘是常态。建索引不是为了“查得快”,而是为了让物理数据天然按分区键有序,跳过切块阶段。
- 没索引时:执行计划里常出现
HashAggregate或WindowAgg节点前置大量Materialize,说明正在物化中间结果 - 有索引但顺序错(比如索引是
(updated_at, dept_id),而窗口写的是PARTITION BY dept_id ORDER BY updated_at)→ 索引无法复用,仍要排序 - MySQL 8.0 和 PostgreSQL 要求索引字段顺序严格匹配
PARTITION BY+ORDER BY,SQL Server 还要求方向一致(DESC必须显式声明)
复合索引怎么写才真正生效
单列索引对ROW_NUMBER几乎没用;必须建联合索引,且字段顺序不能颠倒。核心原则:分区字段最左,排序字段紧随其后,WHERE条件字段可追加在末尾(但别放INCLUDE里——窗口计算需要它参与过滤)。
- 正确示例:
ROW_NUMBER() OVER (PARTITION BY dept_id, team_id ORDER BY updated_at DESC, id ASC)→ 对应索引:CREATE INDEX idx_dept_team_updated_id ON t(dept_id, team_id, updated_at DESC, id ASC) - 错误示例:
CREATE INDEX idx_updated_dept ON t(updated_at DESC, dept_id)→ 分区字段不在最左,优化器直接弃用 - 带WHERE时,比如
WHERE status = 'active',要把status放在索引最左列:(status, dept_id, updated_at DESC),否则过滤动作无法下推到索引扫描阶段
为什么ORDER BY里用函数就彻底失效
索引只加速“原值比较”,一旦ORDER BY里出现UPPER(name)、DATE(created_at)或name || '_v1'这类表达式,数据库就无法用索引顺序直接输出结果,只能回表取原始值再计算,最后强制filesort。
- 现象:EXPLAIN看到
Using filesort和Using temporary同时出现,且rows远大于实际返回行数 - 替代方案:提前物化计算字段并建索引,比如加一列
name_upper VARCHAR(100) STORED,然后建索引(dept_id, name_upper) - NULL值干扰:MySQL默认把NULL排最前,PostgreSQL排最后,导致即使有索引,排序结果也不稳定;建议显式加
NULLS LAST(PostgreSQL)或用COALESCE(field, 'zzzz')兜底(MySQL)
分区键高基数时索引反而没用?
不是索引没用,是窗口函数的并行机制和分区粒度冲突了。当PARTITION BY user_id(百万级唯一值)时,每个分区只有1–2行,数据库会为每个user_id启动一个轻量排序任务,线程调度开销压倒收益。此时索引虽存在,但执行计划里WindowAgg (parallel)节点数量爆炸,CPU利用率飙升,耗时反而更长。
- 诊断方法:EXPLAIN ANALYZE中看到数百个
WindowAgg (parallel)节点,但总执行时间比单线程还高 - 解法不是删索引,而是换策略:若业务只要“最新一条”,改用
GROUP BY + MAX(id)关联;若真需编号,考虑按时间范围预分区(如PARTITION BY DATE(created_at)),降低基数 - 低区分度分区(如
PARTITION BY status只有2值)同样危险:两个超大分区撑爆work_mem,频繁spill到磁盘——这时索引有效,但内存配置成了瓶颈
PARTITION BY和ORDER BY意图。










