sql server分页首选offset-fetch,需配合order by且参数化传值;ef core用skip/take自动翻译,避免内存分页;大数据量时应改用游标分页。

SQL Server 中用 OFFSET-FETCH 做分页最直接
SQL Server 2012+ 支持标准的 OFFSET-FETCH,语义清晰、写法简洁,是首选方案。它不依赖 ROW_NUMBER() 或嵌套子查询,执行计划通常更优。
常见错误是把 OFFSET 和 FETCH 写反顺序,或漏掉 ORDER BY(必须存在,否则报错 "The OFFSET clause is missing in the ORDER BY clause.")。
-
OFFSET @skip ROWS FETCH NEXT @take ROWS ONLY:@skip = (page - 1) * pageSize,@take = pageSize - 注意
OFFSET值为 0 是合法的,但不能为负数;FETCH必须为正整数 - 如果排序字段有重复值,分页结果可能不稳定 —— 加一个唯一列(如主键)到
ORDER BY末尾可规避
Entity Framework Core 中用 Skip() 和 Take() 就够了
EF Core 会自动将 Skip()/Take() 翻译成底层数据库对应的分页语法(SQL Server → OFFSET-FETCH,MySQL → LIMIT,PostgreSQL → LIMIT-OFFSET),无需手动拼 SQL。
容易踩的坑是:在内存中分页(即先 ToList() 再 Skip/Take),尤其数据量大时会导致全表拉取,严重拖慢响应。
- 务必确保
Skip()和Take()在 IQueryable 上调用,且在ToListAsync()之前 - 避免在
Where条件后接AsEnumerable()或ToList()再分页 - 若需动态构建查询,用
IQueryable<t></t>链式传递,别提前触发执行
手写 ADO.NET 分页要防 SQL 注入和类型转换失败
直接拼接 SQL 字符串做分页(比如 "... LIMIT " + pageSize)极不安全,也难维护。正确做法是全部使用参数化查询,包括 OFFSET 和 FETCH 的数值。
常见错误是把页码或页大小当字符串传参,导致 SQL 报错 "Incorrect syntax near '@offset'" —— 因为参数只能用于值,不能用于关键字或结构位置。
- SQL Server:用
OFFSET @skip ROWS FETCH NEXT @take ROWS ONLY,参数类型为int - MySQL:用
LIMIT @take OFFSET @skip,同样必须是MySqlParameter,且OFFSET参数必须为整数(不能是long或string) - 别试图用
string.Format或$""拼接分页参数,这是高危操作
大数据量下 OFFSET 性能会明显下降
当 OFFSET 值很大(比如第 10000 页,每页 20 条 → OFFSET 199980),数据库仍需扫描并跳过前面所有行,IO 和 CPU 开销陡增。
这不是 C# 层能解决的问题,但你得知道什么时候该换策略 —— 比如改用“游标分页”(cursor-based pagination),以最后一条记录的排序字段值作为下一页起点。
- 适合场景:时间线类列表(按
CreateTime DESC)、ID 有序增长的主键列表 - 典型写法:
WHERE CreateTime - 无法跳转任意页,但首屏快、翻页稳定、无性能断崖 —— 大多数前端分页控件其实只支持“下一页”,正好匹配
实际项目里,90% 的分页需求用 EF Core 的 Skip/Take 就足够;真遇到百万级数据翻到深页,得从查询逻辑本身重审,而不是纠结 OFFSET 怎么写更快。










