offset fetch 必须配合 order by 使用,否则报错 msg 102;其语法为 offset (@page-1)*@page_size rows fetch next @page_size rows only,但深度分页性能差,建议改用键集分页。

OFFSET-FETCH 必须配合 ORDER BY 才能用
不写 ORDER BY 直接加 OFFSET 会报错:Msg 102, Level 15, State 1, Line X: Incorrect syntax near 'OFFSET'。SQL Server 要求分页必须有明确的排序依据,否则“第11–20行”这种说法没有意义。
实操建议:
- 排序字段尽量选唯一、非空、有索引的列(如主键
id或带索引的created_at),避免因排序不稳定导致同一页数据重复或遗漏 - 不要用
SELECT *配合大偏移量,尤其当排序字段存在大量重复值时,OFFSET 10000 ROWS可能跳过或重复某些记录 - 如果业务允许,优先用
WHERE id > last_seen_id的游标分页替代 OFFSET-FETCH,性能更稳定
OFFSET-FETCH 的语法结构和常见写法
OFFSET 和 FETCH 是成对出现的,OFFSET 定义跳过的行数,FETCH 定义取多少行。两者顺序不能颠倒,且 FETCH 前必须有 OFFSET。
典型写法示例:
SELECT id, title, created_at FROM posts ORDER BY id DESC OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
注意点:
-
OFFSET 0 ROWS合法,等价于不跳过;但OFFSET后不能跟变量(除非在动态 SQL 中拼接),也不能是表达式(如OFFSET @page * 10 ROWS会报错) -
FETCH NEXT 10 ROWS ONLY中的NEXT是固定关键字,不可省略或替换为FIRST - 不支持
FETCH单独使用,也不支持TOP和OFFSET-FETCH混用
分页参数计算容易出错的地方
前端传来的页码(比如 page=3,每页 size=10),对应到 SQL 应该是 OFFSET (3-1)*10 ROWS,不是 OFFSET 3*10 ROWS。这个 off-by-one 错误非常常见。
实操建议:
- 在应用层统一用
offset = (page - 1) * page_size计算,别依赖 SQL 里做算术 - 对
page和page_size做校验:防止page_size为负数或过大(如超过 100),避免拖慢查询甚至被用于 DoS 攻击 - SQL Server 不支持
LIMIT,也**不支持**OFFSET ... FETCH与FOR XML、FOR JSON等子句混用,会报语法错误
性能问题比想象中更早出现
当 OFFSET 值很大(比如 > 10000),即使有索引,SQL Server 仍需扫描并跳过前面所有行,I/O 和 CPU 开销会明显上升。这不是 bug,是 OFFSET 语义决定的。
可尝试的缓解方式:
- 给
ORDER BY字段建覆盖索引,包含所有 SELECT 列(避免 Key Lookup) - 用
sys.dm_exec_query_stats查看该分页查询的逻辑读是否随 offset 线性增长 - 对高频翻页场景(如后台管理列表),考虑缓存前几页结果,或改用键集分页(keyset pagination)——用上一页最后一条的
id作为下一页起点
真正难处理的是“跳转到第 500 页”这种需求,OFFSET-FETCH 在这里本质是妥协方案,不是银弹。











