用SQLServer咋搞分页查询,数据多了翻页才方便啊
- 问答
- 2026-01-10 19:44:08
- 20
整理自CSDN博客、博客园、知乎等技术社区常见讨论,结合SQLServer官方文档中的基础语法说明)
用SQLServer搞分页查询,确实是个老生常谈但又非常实际的问题,当数据量一大,成千上万条记录,如果一次性全部从数据库里拿出来,前端的页面会卡死,服务器的内存压力也巨大,分页查询的核心思想就是“每次只拿一勺”,而不是“把整个锅端过来”。
在SQLServer中,实现分页查询有几种常见的方法,它们随着SQLServer版本的更新也在不断演进,下面我就按从旧到新、从复杂到简单的顺序,直接跟你说说怎么搞。
最经典(也最啰嗦)的方法:TOP和子查询(适用于早期版本)
在SQLServer 2005及更早的版本里,没有专门的分页命令,大家普遍用一种“土办法”,思路是分两步走:
- 第一步: 先用
TOP关键字取出当前页以及之前所有页的数据,比如你要查第3页,每页10条,那就用TOP 30,取出前30条记录。 - 第二步: 再把前两页的数据(前20条)从这个结果集中排除掉,剩下的就是第3页的10条了,排除的方法通常是用一个子查询,查询出前
(当前页码-1)*每页条数条记录的ID,然后让主查询排除这些ID。
举个例子,假设有张表叫Orders,我们想按OrderID排序,取第3页(每页10行)的数据,SQL写法大概长这样:
-- 假设每页10条,取第3页(即第21到30条)
SELECT TOP 10 * FROM Orders
WHERE OrderID NOT IN (
SELECT TOP 20 OrderID FROM Orders ORDER BY OrderID
)
ORDER BY OrderID;
(来源:早期SQLServer技术博客和论坛的常见解法)
解读一下:
- 里面的子查询
SELECT TOP 20 OrderID FROM Orders ORDER BY OrderID意思是找出按OrderID排序后的前20条订单的ID(也就是第1页和第2页的所有数据)。 - 外面的主查询
SELECT TOP 10 * ... WHERE OrderID NOT IN (...)意思是从整个表里,排除掉刚找到的前20条ID,然后从剩下的数据中再取前10条,因为已经排除了前20条,所以这时候取到的“前10条”自然就是原本的第21到30条了。
这种方法虽然能实现效果,但缺点很明显:
- 写法繁琐: 每次都要写两层查询,排序条件要写两遍,容易出错。
- 性能问题: 当页码非常靠后时,比如要取第1000页,子查询就要先取出TOP 9990条记录的ID,效率会越来越低。
升级版:ROW_NUMBER()窗口函数(SQLServer 2005及以后版本)
从SQLServer 2005开始,引入了一个强大的功能叫窗口函数,其中ROW_NUMBER()函数专门用来给查询结果的每一行生成一个连续的行号,这简直就是为分页量身定做的。
它的思路更直接:

- 先把整个查询结果(或者满足条件的部分)按照你想要的排序规则,给它每一行都标上一个行号(第1行、第2行、第3行...)。
- 把带有行号的这个结果集当作一个临时表(派生表),直接从里面根据行号范围取数据就行了。
还用上面的例子,查Orders表的第3页(每页10条):
-- 使用ROW_NUMBER()
SELECT * FROM (
SELECT *,
ROW_NUMBER() OVER (ORDER BY OrderID) AS RowNum
FROM Orders
) AS TempTable
WHERE RowNum BETWEEN 21 AND 30;
(来源:SQLServer官方文档关于ROW_NUMBER()函数的介绍及后续版本的最佳实践推荐)
解读一下:
ROW_NUMBER() OVER (ORDER BY OrderID) AS RowNum这一部分是在原始表数据上,按照OrderID排序后,为每一行生成一个叫RowNum的连续序号。- 整个
SELECT ..., ROW_NUMBER() ... FROM Orders被包在一个子查询里,相当于我们创建了一个临时表TempTable,这个表比原表多了一列RowNum。 - 最外层的
SELECT * FROM TempTable WHERE RowNum BETWEEN 21 AND 30就非常简单直观了,直接从临时表里筛选出行号在21到30之间的记录。
这种方法比第一种TOP的方法要好得多:
- 逻辑清晰: 分页的逻辑(BETWEEN)一目了然,不容易写错。
- 灵活性高: 排序规则只需要在
OVER子句里写一次,如果想按其他字段排序,改一个地方就行。 - 性能尚可: 在排序字段有索引的情况下,效率不错,尤其适合中等数据量的分页。
现代首选:OFFSET-FETCH子句(SQLServer 2012及以后版本)
如果你用的SQLServer是2012版或更新的版本,那么恭喜你,有了一种最简洁、最符合SQL标准的分页方法——OFFSET-FETCH子句,它直接扩展了ORDER BY子句的功能。
语法超级简单:

ORDER BY 排序列 OFFSET (页码-1)*每页条数 ROWS FETCH NEXT 每页条数 ROWS ONLY;
还是查第3页(每页10条)的例子:
SELECT * FROM Orders ORDER BY OrderID OFFSET 20 ROWS -- 跳过前20行 FETCH NEXT 10 ROWS ONLY; -- 只取接下来的10行
(来源:SQLServer 2012官方发布说明及新特性文档)
解读一下:
OFFSET 20 ROWS:意思很直白,就是跳过前面20行记录,20是怎么来的?(3-1) * 10 = 20。FETCH NEXT 10 ROWS ONLY:跳过之后,从第21行开始,往后取10行。
这是目前最推荐的方法,因为:
- 语法简洁明了: 意图表达得非常清楚,跳过多少,取多少”。
- 性能优化: 数据库引擎可以针对这种分页模式进行更好的优化。
- 符合标准: 这是SQL标准语法,学会它,在其他一些数据库(如PostgreSQL, MySQL 8.0+)中也适用。
实际使用中的一些注意点
不管你用哪种方法,有几个共同的点需要留意:
- 排序是必须的! 分页一定要有一个确定的排序顺序(
ORDER BY),否则数据库每次返回的数据顺序可能不一样,分页就乱套了,最好用唯一性强的列排序(如主键),如果按非唯一列排序,可能出现重复或丢失数据的情况。 - 性能关键在索引: 分页查询的速度快慢,很大程度上取决于你
ORDER BY后面用的那个字段有没有合适的索引,如果没索引,每次分页查询数据库都要对整个表进行排序,数据量一大就慢得不行,给排序字段加索引是优化分页查询最有效的手段。 - 总页数怎么来? 分页通常还需要知道总数据量,用来计算总页数,这个一般需要用另一个查询单独计算:
SELECT COUNT(*) FROM Orders(如果带查询条件,也要加上同样的条件),虽然要执行两次数据库查询,但这是目前通用的做法。
- 老版本(2005以前):凑合着用
TOP和子查询的组合拳。 - 中等版本(2005及以后):优先使用
ROW_NUMBER()函数,逻辑清晰,够用。 - 新版本(2012及以后):毫不犹豫地用
OFFSET-FETCH,这是最爽的方式。
如果你用的SQLServer版本够新,就直接上OFFSET-FETCH吧,代码又好写又容易懂,数据多了翻页才能真正方便起来。
本文由革姣丽于2026-01-10发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://www.haoid.cn/wenda/78252.html
