row_number()分页慢的根本原因是order by字段无对应索引,导致强制sort;必须建与排序完全匹配的联合索引(如order by created_at desc, id desc则建ix_logs_time_id),且表名/列名需quotename防注入,@pageindex/@pagesize须校验越界。

ROW_NUMBER() 在存储过程中做分页,不是“能用就行”,而是必须控制执行路径——否则 5000 行就慢到 20 秒,不是写法错,是没压住执行计划的扫描行为。
为什么 ROW_NUMBER() 分页一查就慢?
常见错误现象:执行时间随页码线性增长,第 1 页 15ms,第 500 页直接 1200ms;执行计划里出现 Index Scan 或 Table Scan,甚至 Sort 运算符占满成本。
- 根本原因不是函数本身,而是
ORDER BY字段没索引——SQL Server 必须先排序再编号,没索引就强制 Sort,I/O 和内存全崩 - 动态拼接
@OrderBy时传入'id DESC, name ASC',但实际表上只有id单列索引,联合排序无法走索引 - WHERE 条件里用了
LIKE '%abc'或函数(如UPPER(name)),导致索引失效,外层 ROW_NUMBER() 只能扫全表再编号
ROW_NUMBER() 分页必须配什么索引?
不是“加个索引就行”,而是索引结构必须和 OVER (ORDER BY ...) 完全对齐,且覆盖 WHERE 中高频过滤字段。
- 若写
ROW_NUMBER() OVER (ORDER BY created_at DESC, id DESC),就必须建联合索引:CREATE INDEX IX_logs_time_id ON logs(created_at DESC, id DESC) - 如果 WHERE 里常带
status = 1,把status加到索引最左列:CREATE INDEX IX_logs_status_time_id ON logs(status, created_at DESC, id DESC) - 避免在排序字段上用表达式,比如
ORDER BY DATEADD(day, 1, created_at)——索引失效,必然触发 Sort
存储过程里怎么防注入、防越界、防溢出?
所有字符串拼接的 @TableName、@OrderBy、@Where 都是 SQL 注入高危点,而计算逻辑稍错就报错或返回空。
- 表名/列名必须用
QUOTENAME(@TableName)和QUOTENAME(@SortColumn)包裹,不能直接拼进 SQL 字符串 -
@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),比硬扛更稳
为什么深度分页(>10 万行偏移)反而推荐 ROW_NUMBER()?
OFFSET FETCH 在 OFFSET 100000 ROWS 时要物理遍历 B+ 树前 10 万行,而 ROW_NUMBER() 可利用索引有序扫描 + StopAt 优化,实际只读需返回的那几十行。
- 关键前提是:外层
WHERE RowNum BETWEEN @StartRow AND @EndRow能被优化器识别为 Seek + Top,这依赖内层排序字段有高效索引 - 别名必须显式声明:
ROW_NUMBER() OVER (...) AS rn,否则外层引用t.rn报Invalid column name 'rn' - 不要用
SELECT *套两层——内层 CTE 或子查询只选必要字段,尤其避开text/xml大字段,否则内存 grant 溢出直接失败
真正卡住性能的从来不是 ROW_NUMBER() 这个函数,而是你没让它的执行路径落在索引有序扫描上。只要 ORDER BY 字段没对应索引,或者 WHERE 条件破坏了索引可用性,再干净的写法也救不回来。











