SQL Server 2012使用OFFSET/FETCH NEXT分页及性能测试

本文对比了SQLServer2012中OFFSET/FETCH NEXT与ROW_NUMBER()两种分页方法的性能,并通过实验数据展示了在不同场景下两者的实际表现。实验证明,OFFSET/FETCH NEXT在减少I/O操作方面更胜一筹。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在网上看到不少文章介绍使用SQL Server 2012的新特性:OFFSET/FETCH NEXT 实现分页。多数文章都是引用或者翻译的这一篇《SQL Server 2012 - Server side paging demo using OFFSET/FETCH NEXT》,原文地址

邀月对此也做了性能测,《SQL Server 2012服务端使用OFFSET/FETCH NEXT实现分页》,不过老外或者邀月的代码都并没有真正显示出OFFSET/FETCH NEXT的性能比起原有的ROW_NUMBER()方式好多少。

我试了下,发现主要是在取COUNT(*)上,如果OFFSET/FETCH NEXT也同时取COUNT(*),那么执行计划里一样是聚集索引Scan或者表扫描。如果不同时取COUNT(*),那么性能提升相当可观。

初始化脚本如下,生成200w条记录,用CTE递归插入,邀月的版权,^_^

复制代码
USE DBAdmin
GO
/*
Setup script to create the sample table and fill it with
sample data.
*/
IF OBJECT_ID('Customers','U') IS NOT NULL
DROP TABLE Customers

USE [DBAdmin]
GO

CREATE TABLE [dbo].[Customers](
    [CustomerID] [int] IDENTITY(1,1) NOT NULL,
    [CustomerNumber] [char](8) NULL,
    [CustomerName] [varchar](50) NULL,
    [CustomerCity] [varchar](20) NULL,
 CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED 
(    [CustomerID] ASC ) ) ON [PRIMARY]

GO

TRUNCATE table Customers
GO

DBCC DROPCLEANBUFFERS
DBCC FREEPROCCACHE
GO
/*****运用CTE递归插入,速度较快,邀月注***********************/
WITH Seq (num,CustomerNumber, CustomerName, CustomerCity) AS
(SELECT 1,cast('00000000'as CHAR(8)),cast('Customer 0' AS NVARCHAR(50)),cast('X-City' as NVARCHAR(20))
UNION ALL
SELECT num + 1,Cast(REPLACE(STR(num, 8), ' ', '0') AS CHAR(8)),
cast('Customer ' + STR(num,8) AS NVARCHAR(50)),
cast(CHAR(65 + (num % 26)) + '-City' AS NVARCHAR(20))
FROM Seq
WHERE num <= 2000000
)
INSERT INTO Customers (CustomerNumber, CustomerName, CustomerCity)
SELECT CustomerNumber, CustomerName, CustomerCity
FROM Seq
OPTION (MAXRECURSION 0)
复制代码


2005或者2008下,使用ROW_NUMBER()分页,我把Count(*)的部分修改了:

复制代码
/*
Server side paging demo using ROW_NUMBER() - SQL Server
2005/2008 version.
*/
DBCC DROPCLEANBUFFERS
DBCC FREEPROCCACHE

SET STATISTICS IO ON;
SET STATISTICS TIME ON;
GO

DECLARE @page INT, @size INT ,@Total int 
SELECT @page = 700, @size = 10

select @Total = COUNT(*) 
FROM Customers
WHERE CustomerCity IN ('A-City','B-City')

;WITH cte AS (
SELECT TOP (@page * @size)
CustomerID,
CustomerNumber,
CustomerName,
CustomerCity,
ROW_NUMBER() OVER(ORDER BY CustomerName ) AS Seq --,COUNT(*) OVER(PARTITION BY '') AS Total
FROM Customers
WHERE CustomerCity IN ('A-City','B-City')
ORDER BY CustomerName ASC
)
SELECT CustomerID,CustomerNumber,CustomerName,CustomerCity,@Total
FROM cte
WHERE seq BETWEEN (@page - 1 ) * @size + 1 AND @page * @size
ORDER BY seq;
GO

SET STATISTICS IO OFF ;
SET STATISTICS TIME OFF;
GO 
复制代码

CPU及IO:

复制代码
DBCC execution completed. If DBCC printed error messages, contact your system administrator.
DBCC execution completed. If DBCC printed error messages, contact your system administrator.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 47 ms.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.
Table 'Customers'. Scan count 5, logical reads 12600, physical reads 1, read-ahead reads 12468, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 483 ms,  elapsed time = 1615 ms.

(10 row(s) affected)
Table 'Customers'. Scan count 5, logical reads 12648, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 671 ms,  elapsed time = 183 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.
复制代码

 

2012下,使用OFFSET/FETCH NEXT分页,同样,我把Count(*)的部分修改了以及ORDER BY CustomerName:

复制代码
/*
Server side paging demo using the new enhancements added
in SQL Server 2012
*/
DBCC DROPCLEANBUFFERS
DBCC FREEPROCCACHE

SET STATISTICS IO ON;
SET STATISTICS TIME ON;
GO

DECLARE @page INT, @size INT,@Total int 
SELECT @page = 700, @size = 10

select @Total = COUNT(*) 
FROM Customers
WHERE CustomerCity IN ('A-City','B-City')

SELECT
*,@Total--,COUNT(*) OVER(PARTITION BY '') AS Total
FROM Customers
WHERE CustomerCity IN ('A-City','B-City')
ORDER BY CustomerName  
OFFSET (@page -1) * @size ROWS
FETCH NEXT @size ROWS ONLY;
GO

SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;
GO
复制代码

 

CPU及IO:

复制代码
DBCC execution completed. If DBCC printed error messages, contact your system administrator.
DBCC execution completed. If DBCC printed error messages, contact your system administrator.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 26 ms.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.
Table 'Customers'. Scan count 5, logical reads 12600, physical reads 1, read-ahead reads 12468, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 266 ms,  elapsed time = 1688 ms.

(10 row(s) affected)
Table 'Customers'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 SQL Server Execution Times:
   CPU time = 639 ms,  elapsed time = 175 ms.
SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

 SQL Server Execution Times:
   CPU time = 0 ms,  elapsed time = 0 ms.
复制代码


可以看到IO有很大的变化,而CPU略有提升。当然也可以在CustomerCity上加上如下索引:

CREATE NONCLUSTERED INDEX IX_Customers_CustomerCity
ON [dbo].[Customers] ([CustomerCity])
INCLUDE ([CustomerNumber],[CustomerName])

 

加完索引后依然是OFFSET/FETCH NEXT的性能要更好。新特性使得分页的性能提升不少,.net程序员们的福音啊

<think>好的,用户问的是SQL Server如何使用OFFSET进行分页。我得先回忆一下OFFSET-FETCH子句的用法。记得在SQL Server2012及更高版本中,OFFSETFETCH是用于分页的关键字,通常和ORDER BY一起使用。需要确保用户知道这个语法适用的版本,避免他们在旧版本上尝试使用导致错误。 首先,应该给出一个基本的示例,展示如何跳过前N行并获取接下来的M行。比如分页查询第二页,每页10条,那么应该是跳过前10条,取接下来的10条。这时候的SQL语句结构应该是SELECT... ORDER BY ...OFFSET10 ROWS FETCH NEXT 10ROWS ONLY。需要明确说明OFFSET后面的数字是跳过的行数,FETCH则是获取的行数。然后可能需要解释参数化查询的重要性,比如在应用程序中,页码和每页数量通常是动态的,使用参数可以防止SQL注入,并且提高查询的重用性。这里可以展示一个使用变量代替固定值的例子,比如@PageNumber和@PageSize,然后计算OFFSET的值。 接下来,性能优化也是一个重点。当处理大数据量分页时,OFFSET的效率可能会下降,因为需要扫描并跳过前面的行。这时候可能需要提到使用索引,特别是覆盖索引,来优化排序和查询的速度。例如,在OrderDate字段上建立索引,可以加快ORDER BY的操作,减少查询时间。 还有,分页时的总记录数获取问题。通常分页需要知道总共有多少条记录,以便计算总页数。这时候可以给出一个例子,使用窗口函数COUNT(*)OVER()来在同一个查询中获取总记录数,避免额外的查询次数。 最后,可能需要提醒用户关于排序一致性的问题。如果没有正确的ORDER BY,分页的结果可能会不一致,导致重复或遗漏数据。所以必须强调ORDER BY子句的重要性,并且最好使用唯一键来确保排序的确定性,比如在OrderDate相同的情况下,加上另一个唯一字段如OrderID。现在,把这些点整理成一个结构化的回答,包括代码示例和解释,然后提出相关问题,比如不同版本的分页方法、性能比较、参数化查询的好处以及处理大数据量的替代方案。确保问题覆盖用户可能感兴趣的扩展方向。</think>SQL Server2012版本开始支持`OFFSET`语法实现高效分页,以下是典型的分页查询实现: ```sql -- 获取第2页数据(每页10条) SELECT OrderID, OrderDate, CustomerID FROM Orders ORDER BY OrderDate DESC OFFSET 10 ROWS -- 跳过前10条(第1页) FETCH NEXT 10 ROWS ONLY; -- 取接下来的10条 ``` **代码解释:** 1. `ORDER BY`是必须的,用于确定数据顺序 2. `OFFSET`指定跳过的行数(计算方式:`(页码-1)*每页数量`) 3. `FETCH NEXT`指定需要获取的行数(即每页显示数量) **参数化分页查询示例:** ```sql DECLARE @PageNumber INT = 2 DECLARE @PageSize INT = 10 SELECT OrderID, OrderDate, CustomerID FROM Orders ORDER BY OrderDate DESC OFFSET (@PageNumber - 1) * @PageSize ROWS FETCH NEXT @PageSize ROWS ONLY; ``` **性能优化建议:** 1. 在排序字段(如OrderDate)上创建索引 2. 对于大数据表,考虑使用覆盖索引 3. 避免在WHERE条件中使用复杂过滤 4. 获取总记录数的优化方式: ```sql SELECT *, TotalCount = COUNT(*) OVER() FROM Orders ORDER BY OrderDate DESC OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值