ROW_NUMBER()分页慢的根源是执行计划未优化:需按ORDER BY字段建匹配索引、WHERE条件下推至内层子查询、严格校验参数并拦截深分页。

ROW_NUMBER() 分页不慢,慢是因为没压住执行计划——它必须配合索引、过滤下推和参数校验才能真正高效。
为什么 ROW_NUMBER() 分页越往后越卡
第 1 页查得快,第 500 页直接卡住,常见错误现象是执行计划里出现 Sort 运算符占满成本,或 Index Scan / Table Scan 扫全表。根本原因不是函数本身,而是 ORDER BY 字段没建对索引:SQL Server 必须先排序再编号,没索引就强制 Sort,I/O 和内存双双崩盘。
-
ORDER BY created_at DESC, id DESC却只建了IX_logs_created_at单列索引 → 联合排序无法走索引 -
WHERE name LIKE '%abc'或UPPER(name) = 'ABC'→ 索引失效,外层ROW_NUMBER()只能扫全表再编号 -
ORDER BY DATEADD(day, 1, created_at)→ 表达式导致排序字段无法命中索引
必须配什么索引才真正生效
不是“加个索引就行”,而是索引结构必须和 OVER (ORDER BY ...) 完全对齐,且覆盖高频 WHERE 条件字段。
- 若写
ROW_NUMBER() OVER (ORDER BY status, updated_at DESC),就必须建CREATE INDEX IX_posts_status_updated ON posts(status, updated_at DESC) - 如果常带
WHERE category_id = 5,把category_id加到索引最左列:CREATE INDEX IX_posts_cat_status_updated ON posts(category_id, status, updated_at DESC) - 排序字段顺序、方向(
ASC/DESC)必须和OVER子句严格一致,否则优化器可能弃用索引
存储过程里怎么防注入、防越界、防错位
所有动态拼接的 @TableName、@OrderBy、@Where 都是高危点;@PageIndex 和 @PageSize 算错一步,结果就全偏。
- 表名/列名必须用
QUOTENAME(@TableName)和QUOTENAME(@SortColumn)包裹,绝不能直接拼进字符串 -
@PageIndex和@PageSize提前校验:IF @PageIndex ;<code>IF @PageSize > 100 SET @PageSize = 100 - 起始行计算必须是
DECLARE @StartRow INT = (@PageIndex - 1) * @PageSize + 1—— 漏掉+ 1,第 1 页就跳过第 1 条 - 大偏移量主动拦截:
IF (@PageIndex > 5000) RAISERROR('Deep page not allowed', 16, 1),比硬扛更稳
嵌套结构里 WHERE 条件该写在哪一层
漏写或错放 WHERE,会导致分页逻辑彻底失效:外层 rn BETWEEN 过滤的是“全表编号后的行号”,不是“业务数据的行号”。
- ✅ 正确:把业务条件全部塞进最内层查询,让
ROW_NUMBER()只给符合条件的行编号SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY id) AS rn FROM orders WHERE status = 'shipped') t WHERE t.rn BETWEEN 11 AND 20 - ❌ 错误:条件写在外层,编号已按全表做完,
rn = 11可能对应一条status = 'pending'记录SELECT * FROM (SELECT *, ROW_NUMBER() OVER (ORDER BY id) AS rn FROM orders) t WHERE t.status = 'shipped' AND t.rn BETWEEN 11 AND 20 - 子查询里别写
SELECT *,只选分页需要的字段 + 排序字段,减少临时结果集体积
真正难的不是写出 ROW_NUMBER() 语句,而是让执行计划里看到 Index Seek 而不是 Scan,看到 Top N Sort 而不是全量 Sort,以及确保每一页返回的数据既稳定又不重不漏——这些都藏在索引定义、条件位置和参数边界里,稍一松动就全盘失准。

















