根本原因是over子句中order by强制全量排序,即使只取row_number()=1的一行;索引必须按partition by、order by、where字段严格顺序创建,且range或空order by会隐式触发全表排序。

OVER子句强制全量排序,无法跳过
根本原因不是语法写错,而是 OVER 里带 ORDER BY 就必须对每个分区做完整排序——哪怕你只取 ROW_NUMBER() = 1 的一行,数据库也得先把整个分区排完序再编号。
常见错误现象:SELECT TOP 1 * 很快,但套上 ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) 就变慢几倍,就是因为排序不可省略。
- 分区数越多(比如按
user_id分),CPU 和内存调度开销越分散,比单次大排序更伤 - 分区字段区分度低(如只有
'active'/'inactive'),会导致两三个超大分区,极易触发Sort溢出到 TempDB -
ORDER BY含函数(如UPPER(name))或表达式,索引完全失效,必然全表扫描 + 文件排序
索引没对上 PARTITION BY + ORDER BY 顺序
建了索引但还是慢?大概率是索引字段顺序错了。SQL Server 不会用 (salary, dept) 加速 PARTITION BY dept ORDER BY salary,它只能用 (dept, salary)。
关键规则:索引键列必须严格按 PARTITION BY 字段(最左)、ORDER BY 字段(次左)、WHERE 条件字段(再后)排列,且 ASC/DESC 方向要一致。
- 错误示例:
CREATE INDEX idx ON emp (salary DESC, dept)—— 对OVER (PARTITION BY dept ORDER BY salary DESC)无效 - 正确写法:
CREATE NONCLUSTERED INDEX idx_dept_salary ON emp (dept, salary DESC) INCLUDE (id, name) - 如果还有
WHERE status = 1,应把status放进键列(不是INCLUDE),否则仍需回表过滤
空 ORDER BY 或 RANGE 框架引发隐式开销
ROW_NUMBER() OVER (PARTITION BY dept) 看似合法,实则危险:SQL Server 会按物理顺序任意编号,执行计划中出现全表扫描 + 隐式排序节点,结果还不稳定。
RANGE BETWEEN 比 ROWS BETWEEN 更容易拖慢查询,尤其在有重复排序值时——它每行都要重查“哪些值落在范围内”,最坏是 O(n²) 复杂度。
- 时间类窗口(如“过去7天”)必须用
RANGE,但务必给event_time建索引,并避免和LEAD/LAG混用(SQL Server 2019 报错Msg 116) - 主键、时间戳递增等场景,一律显式写
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW -
COUNT(*) OVER ()这种空括号写法,会强制全局排序,表无聚集索引时开销极大
嵌套子查询放大排序规模
写成 SELECT * FROM (SELECT *, ROW_NUMBER() OVER (...) AS rn FROM t WHERE ...) t2 WHERE rn = 1,执行顺序是:先过滤 → 再对全部过滤结果做分区+排序 → 最后才取第 1 行。中间排序的数据量可能远超最终返回行数。
真正省资源的做法不是靠 rn = 1 截断,而是让排序本身变轻:
- 把
WHERE条件尽量前移到子查询外层,减少参与排序的行数 - 确认
PARTITION BY字段是否真有必要;若只需全局 Top N,优先考虑TOP N+ORDER BY - 同一窗口逻辑被多次引用(如同时算
SUM() OVER和AVG() OVER),用 CTE 提前计算并复用,避免重复排序
真正卡住性能的,往往不是没建索引,而是索引没覆盖 PARTITION BY 和 ORDER BY 的联合顺序,或者没意识到 RANGE 和空 ORDER BY 会悄悄触发全量排序。执行计划里只要看到红色 Sort 节点或 Spill to TempDB,基本就是它在拖慢。










