SQL Server 高效分页查询方案对比与存储过程
本文通过真实数据对比 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。
性能总结与建议
根据上述测试结果:
- 页码靠前时:效率排名约为 3 > 4 > 5 > 2 > 1。
- 页码靠后时:效率排名约为 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 性能优化的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













