offset fetch 越翻越卡因每次需从头扫描并跳过前n行,大数据量下i/o与cpu压力剧增;需建联合索引、精简字段、优先用游标分页。

为什么 OFFSET FETCH 越翻越卡
因为 OFFSET 每次都得从头扫表、跳过前 N 行,数据量大时 I/O 和 CPU 都扛不住。尤其在没有覆盖索引、或者排序字段无有效索引时,ORDER BY id DESC OFFSET 100000 ROWS FETCH NEXT 20 ROWS ONLY 可能要扫描十几万行才取到那 20 条。
- 真实场景:用户滚动加载评论,第 5000 页开始明显卡顿
- 关键瓶颈:数据库无法跳过已排序结果的前半部分,必须物理跳行
- 索引无效的典型情况:
ORDER BY created_at DESC, id DESC但只对created_at建了单列索引
ROW_NUMBER() 不是万能解药,得配索引用
直接套 ROW_NUMBER() OVER (ORDER BY id DESC) 还是慢——如果子查询没走索引,先生成百万行编号再过滤,反而更糟。真正起效的前提是:外层 WHERE 能下推、内层排序能命中索引。
- 必须给
ORDER BY字段建联合索引,例如分页按status, updated_at DESC,就建INDEX idx_status_updated ON table_name (status, updated_at DESC) - 子查询里别写
SELECT *,只选分页需要的字段 + 排序字段,减少临时结果集体积 - 示例写法:
SELECT id, title, updated_at FROM ( SELECT id, title, updated_at, ROW_NUMBER() OVER (ORDER BY status, updated_at DESC) AS rn FROM posts WHERE status = 1 ) t WHERE rn BETWEEN 10001 AND 10020;
游标分页(Cursor-based)比 ROW_NUMBER() 更稳
当排序字段有唯一性保障(比如 id 或组合 updated_at, id),用游标分页能彻底避开行号计算,每次只查“比上一页最后一条更新”的下一批。
- 适用前提:排序字段组合能唯一标识一行(避免漏/重),且前端能传回上一页末尾值
- 典型错误:用
updated_at单字段做游标,但同一秒有多条记录 → 丢数据 - 安全写法:
WHERE (updated_at, id) ,配合 <code>INDEX (updated_at DESC, id DESC) - 性能差异:10 万页后,游标查询仍稳定在 5ms 内;
ROW_NUMBER()可能升到 800ms+
SQL Server 的 OFFSET 实际表现比想象中好一点
SQL Server 2016+ 对 OFFSET 做了优化,如果排序字段有索引、且 FETCH 行数小(比如 ≤ 100),执行计划里可能看到 “Top N Sort” 而非全量排序。但这不改变本质:OFFSET 仍是线性成本,只是系数变小了。
- 别信“加个索引 OFFSET 就快”,得看执行计划里有没有
Seek,而不是Scan - 对比测试建议:用
SET STATISTICS IO ON看逻辑读,翻到第 1 万页时,OFFSET逻辑读常是游标的 3–5 倍 - 兼容性注意:SQL Server 2005 不支持
OFFSET,老系统硬切ROW_NUMBER()前,先确认版本
最常被忽略的一点:分页慢往往不是语法问题,而是排序字段没索引,或索引顺序和查询方向相反(比如索引是 ASC,却 ORDER BY ... DESC)。先看执行计划里的排序操作是否走了索引 Seek,再动逻辑。










