SQL Server 高效分页查询方案对比与存储过程

洛溪
洛溪
发布时间:2026-10-14| 5人浏览过
原创

本文通过真实数据对比 SQL Server 中五种常见分页查询方案的执行效率,分析不同场景下(靠前页码与靠后页码)各方案的优劣,并提供基于高性能思路编写的通用分页存储过程。

测试环境与背景

为了客观评估分页查询性能,我们搭建了一个基于客户真实数据的测试环境:

  • 数据库版本:SQL Server 2008 R2
  • 数据量:53,210 条记录
  • 表结构:ARTICLE 表,包含 ID(聚集索引)、YEAR(无索引)及其他字段
  • 测试条件:每页 30 条数据
  • 核心指标:平均查询 100 次所需时间

我们将分别对“第 1500 页”(靠后页码,即第 45001-45030 条)和“第 1000-1030 条附近”(相对靠前页码)两种情况进行测试。

方案一:简单普通方法(NOT IN)

这是最直观但通常性能最差的方式,利用 NOT IN 排除已存在的 ID。

SELECT TOP 30 * FROM ARTICLE 
WHERE ID NOT IN(SELECT TOP 45000 ID FROM ARTICLE ORDER BY YEAR DESC, ID DESC) 
ORDER BY YEAR DESC,ID DESC

测试结果:

  • 第 1500 页查询耗时:45s (100次平均)
  • 第 1000 条附近查询耗时:80s (100次平均)

结论:随着页码增加,嵌套的子查询导致扫描行数急剧上升,性能不可接受。

方案二:三层嵌套 TOP

通过多层嵌套 TOP 并结合排序反转来获取中间数据。

SELECT * FROM (  
    SELECT TOP 30 * FROM (SELECT TOP 45030 * FROM ARTICLE ORDER BY YEAR DESC, ID DESC) f 
    ORDER BY f.YEAR ASC, f.ID DESC) s 
ORDER BY s.YEAR DESC,s.ID DESC

测试结果:

  • 第 1500 页查询耗时:138s (100次平均)
  • 第 1000 条附近查询耗时:30s (100次平均)

结论:该方案在处理靠后页码时效率极低,甚至不如方案一,不推荐用于大数据量深分页。

方案三:子查询关联(JOIN 风格)

先查询需要的 ID 集合,再与原表关联获取完整字段。

SELECT * FROM ARTICLE w1, 
( SELECT TOP 30 ID FROM 
    ( SELECT TOP 50030 ID, YEAR FROM ARTICLE ORDER BY YEAR DESC, ID DESC 
    ) w ORDER BY w.YEAR ASC, w.ID ASC 
) w2 WHERE w1.ID = w2.ID ORDER BY w1.YEAR DESC, w1.ID DESC

测试结果:

  • 第 1500 页查询耗时:21s (100次平均)
  • 第 1000 条附近查询耗时:12s (100次平均)

方案四:IN 子查询关联

与方案三逻辑类似,但使用 IN 进行过滤。

SELECT * FROM ARTICLE w1 
WHERE ID in ( 
    SELECT top 30 ID FROM 
    ( SELECT top 45030 ID, YEAR FROM ARTICLE ORDER BY YEAR DESC, ID DESC 
    ) w ORDER BY w.YEAR ASC, w.ID ASC 
) 
ORDER BY w1.YEAR DESC, w1.ID DESC

测试结果:

  • 第 1500 页查询耗时:20s (100次平均)
  • 第 1000 条附近查询耗时:13s (100次平均)

对比:方案三和方案四在实际操作中表现非常接近,均优于方案一和二。

方案五:ROW_NUMBER() 窗口函数

利用 SQL Server 2005+ 引入的 ROW_NUMBER() 为行编号,再通过编号范围筛选。这是目前处理 SQL Server 分页的主流且高效的方法之一。

SELECT w2.n, w1.* FROM ARTICLE w1, (   
    SELECT TOP 50030 row_number() OVER (ORDER BY YEAR DESC, ID DESC) n, ID FROM ARTICLE 
) w2 WHERE w1.ID = w2.ID AND w2.n > 50000 ORDER BY w2.n ASC

测试结果:

  • 第 1500 页查询耗时:15s (100次平均)
  • 第 1000 条附近查询耗时:14s (100次平均)

结论:在页码较深的情况下(如第1500页),方案五表现最佳(15s);在页码较近时(如第1000条附近),方案三、四略优或持平。考虑到综合性能和可读性,方案五是最佳选择。注意:此方法要求 SQL Server 2005 及以上版本,不支持 SQL 2000。

性能总结与建议

根据上述测试结果:

  1. 页码靠前时:效率排名约为 3 > 4 > 5 > 2 > 1。
  2. 页码靠后时:效率排名约为 5 > 4 > 3 > 1 > 2。

由于大多数用户检索集中在前面几页,方案 3、4、5 均可胜任。但若需兼顾所有场景及未来数据增长,推荐使用方案五。

通用分页存储过程实现

基于方案四的逻辑,我们可以编写一个通用的分页存储过程,适用于各种表和查询条件。该存储过程支持动态生成 SQL,灵活处理排序、条件和分页参数。

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[sys_Page_v2]') and OBJECTPROPERTY(id, N'IsProcedure') = 1) 
drop procedure [dbo].[sys_Page_v2]
GO
CREATE PROCEDURE [dbo].[sys_Page_v2] 
@PCount int output, --总页数输出 
@RCount int output, --总记录数输出 
@sys_Table nvarchar(100), --查询表名 
@sys_Key varchar(50), --主键 
@sys_Fields nvarchar(500), --查询字段 
@sys_Where nvarchar(3000), --查询条件 
@sys_Order nvarchar(100), --排序字段 
@sys_Begin int, --开始位置 
@sys_PageIndex int, --当前页数 
@sys_PageSize int --页大小 
AS 
SET NOCOUNT ON 
SET ANSI_WARNINGS ON 
IF @sys_PageSize < 0 OR @sys_PageIndex < 0 
BEGIN 
RETURN 
END 
DECLARE @new_where1 NVARCHAR(3000) 
DECLARE @new_order1 NVARCHAR(100) 
DECLARE @new_order2 NVARCHAR(100) 
DECLARE @Sql NVARCHAR(4000) 
DECLARE @SqlCount NVARCHAR(4000) 
DECLARE @Top int 
if(@sys_Begin <=0) 
set @sys_Begin=0 
else 
set @sys_Begin=@sys_Begin-1 
IF ISNULL(@sys_Where,'') = '' 
SET @new_where1 = ' ' 
ELSE 
SET @new_where1 = ' WHERE ' + @sys_Where 
IF ISNULL(@sys_Order,'') <> '' 
BEGIN 
SET @new_order1 = ' ORDER BY ' + Replace(@sys_Order,'desc','') 
SET @new_order1 = Replace(@new_order1,'asc','desc') 
SET @new_order2 = ' ORDER BY ' + @sys_Order 
END 
ELSE 
BEGIN 
SET @new_order1 = ' ORDER BY ID DESC' 
SET @new_order2 = ' ORDER BY ID ASC' 
END 
SET @SqlCount = 'SELECT @RCount=COUNT(1),@PCount=CEILING((COUNT(1)+0.0)/'+CAST(@sys_PageSize AS NVARCHAR)+') FROM '+ @sys_Table + @new_where1 
EXEC SP_EXECUTESQL @SqlCount,N'@RCount INT OUTPUT,@PCount INT OUTPUT',@RCount OUTPUT,@PCount OUTPUT 
IF @sys_PageIndex > CEILING((@RCount+0.0)/@sys_PageSize) 
BEGIN 
SET @sys_PageIndex = CEILING((@RCount+0.0)/@sys_PageSize) 
END 
set @sql = 'select '+ @sys_fields +' from ' + @sys_Table + ' w1 '+' where '+ @sys_Key +' in ('+'select top '+ ltrim(str(@sys_PageSize)) +' '+ @sys_Key + ' from '+'('+ 'select top ' + ltrim(STR(@sys_PageSize * @sys_PageIndex + @sys_Begin)) + ' '+ @sys_Key + ' FROM '+ @sys_Table + @new_where1 + @new_order2 +') w '+ @new_order1 +') '+ @new_order2 
print(@sql) 
Exec(@sql) 
GO

使用提示:调用时需提供表名、主键、查询字段、条件、排序规则以及分页参数。该过程会自动计算总页数和总记录数,并返回当前页数据。

以上就是 SQL Server 高效分页查询方案的详细内容,更多关于 SQL Server 性能优化的资料请关注本站其它相关文章!

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。