在 SQL Server 中是否有更好的选项来应用分页而不应用 OFFSET?

问题描述 投票:0回答:4

我想对数据量很大的表应用分页。我只想知道比在 SQL Server 中使用 OFFSET 更好的选择。

这是我的简单查询:

SELECT *
FROM TableName
ORDER BY Id DESC 
OFFSET 30000000 ROWS
FETCH NEXT 20 ROWS ONLY
sql sql-server pagination keyset-pagination
4个回答
46
投票

您可以为此使用Keyset Pagination它比使用Rowset Pagination(按行号分页)更高效

在行集分页中,必须读取之前的所有行,然后才能读取下一页。而在键集分页中,服务器可以“立即”跳转到索引中的正确位置,因此不会读取不需要的额外行。 为了使其性能良好,您需要在该键上有一个唯一索引,其中包括您需要查询的任何其他列。

在这种类型的分页中,您无法跳转到特定页码。您跳转到特定的键并从那里开始阅读。因此,您需要保存当前页面的唯一 ID,然后跳到下一个页面。或者,您可以预先计算或估计每个页面的起点。

除了明显的效率提升之外,还有一个很大的好处

,那就是避免分页时由于从先前读取的页面中删除行而导致的“缺失行”问题。按键分页时不会发生这种情况,因为键不会改变。


这是一个例子:

让我们假设您有一个名为

TableName

的表,其索引位于

Id
,并且您希望从最新的
Id
值开始并向后工作。
你开始于:

SELECT TOP (@numRows) * FROM TableName ORDER BY Id DESC;

注意使用
ORDER BY

以确保顺序正确

在某些 RDBMS 中,您需要 

LIMIT

而不是

TOP

客户端将保留最后收到的
Id

值(在本例中为最低值)。在下一个请求时,您跳转到该键并继续:

SELECT TOP (@numRows)
  *
FROM TableName
WHERE Id < @lastId
ORDER BY Id DESC;

注意使用 
<

而不是

<=

如果您想知道,在典型的 B-Tree+ 索引中,具有指示 ID 的行

被读取,而是其之后的行被读取。


选择的键

必须是唯一的,因此,如果您按非唯一列进行分页,则必须向ORDER BY

WHERE
添加第二列。例如,您需要
OtherColumn, Id
上的索引来支持此类查询。不要忘记索引上的
INCLUDE
列。
SQL Server 不支持 

行/元组比较器

,因此您无法执行 (OtherColumn, Id) < (@lastOther, @lastId)(不过 PostgreSQL、MySQL、MariaDB 和 SQLite 均支持此操作)。

您需要以下内容:

SELECT TOP (@numRows) * FROM TableName WHERE ( (OtherColumn = @lastOther AND Id < @lastId) OR OtherColumn < @lastOther ) ORDER BY OtherColumn DESC, Id DESC;

这比看起来更有效,因为 SQL Server 可以将其转换为两个值的正确 
<

NULL

的存在使事情变得更加复杂。您可能想单独查询这些行。

    


8
投票

让我用一个明显的例子来谈谈。

我们的桌子是这样设计的:

CREATE TABLE S_TEMP.T_PAGINATION_PGN (PGN_ID BIGINT IDENTITY(-9 223 372 036 854 775 808, 1) PRIMARY KEY, PGN_SESSION_GUID UNIQUEIDENTIFIER NOT NULL, PGN_SESSION_DATE DATETIME2(0) NOT NULL, PGN_PRODUCT_ID INT NOT NULL, PGN_SESSION_ORDER INT NOT NULL); CREATE INDEX X_PGN_SESSION_GUID_ORDER ON S_TEMP.T_PAGINATION_PGN (PGN_SESSION_GUID, PGN_SESSION_ORDER) INCLUDE (PGN_SESSION_ORDER); CREATE INDEX X_PGN_SESSION_DATE ON S_TEMP.T_PAGINATION_PGN (PGN_SESSION_DATE);

我们有一个非常大的产品表,称为 T_PRODUIT_PRD,并且客户使用许多谓词对其进行了过滤。我们以这种方式将过滤后的 SELECT 中的行插入到该表中:

DECLARE @SESSION_ID UNIQUEIDENTIFIER = NEWID(); INSERT INTO S_TEMP.T_PAGINATION_PGN SELECT @SESSION_ID , SYSUTCDATETIME(), PRD_ID, ROW_NUMBER() OVER(ORDER BY --> custom order by FROM dbo.T_PRODUIT_PRD WHERE ... --> custom filter

然后,每当我们需要所需的页面、@N 产品的复合时,我们都会向该表添加一个联接:

... JOIN S_TEMP.T_PAGINATION_PGN ON PGN_SESSION_GUID = @SESSION_ID AND 1 + (PGN_SESSION_ORDER / @N) = @DESIRED_PAGE_NUMBER AND PGN_PRODUCT_ID = dbo.T_PRODUIT_PRD.PRD_ID

所有索引都可以完成这项工作!

当然,我们必须定期清除此表,这就是为什么我们有一个计划作业来删除 4 小时前生成会话的行:

DELETE FROM S_TEMP.T_PAGINATION_PGN WHERE PGN_SESSION_DATE < DATEADD(hour, -4, SYSUTCDATETIME());



0
投票

WITH CTE AS (SELECT 30000000 AS N UNION ALL SELECT N-1 FROM CTE WHERE N > 30000000 +1 - 20) SELECT T.* FROM CTE JOIN TableName T ON CTE.N=T.ID ORDER BY CTE.N DESC

尝试了 20 亿行,它是即时的!
很容易使它成为存储过程......
当然,如果 id 相互跟随,则有效。


0
投票

CREATE proc [dbo].[GetTransDetails]( @PageNo int = 1, @PageSize int)as BEGIN declare @idfrom int=1 declare @idto int=30 //number of rows SET NOCOUNT ON; if @PageNo>1 begin set @idfrom=(@PageNo*30)-29 set @idto=@PageNo*30 end select top 30 * from (select ROW_NUMBER() OVER (ORDER BY id desc) AS rownumber, * FROM transdetails )transList where transList.rownumber between @idfrom and @idto END

	
© www.soinside.com 2019 - 2024. All rights reserved.