Md.*_*kot 24 sql sql-server pagination keyset-pagination
我想对包含大量数据的表应用分页。我只想知道比在 SQL Server 中使用 OFFSET 更好的选择。
这是我的简单查询:
SELECT *
FROM TableName
ORDER BY Id DESC
OFFSET 30000000 ROWS
FETCH NEXT 20 ROWS ONLY
Run Code Online (Sandbox Code Playgroud)
Cha*_*ace 44
您可以为此使用键集分页。它比使用 Rowset Pagination(按行号分页)要高效得多。
在行集分页中,必须先读取所有先前的行,然后才能读取下一页。而在键集分页中,服务器可以立即跳转到索引中的正确位置,因此不会读取不需要的额外行。
为了使其性能良好,您需要在该键上有一个唯一索引,其中包括您需要查询的任何其他列。
在这种类型的分页中,您无法跳转到特定页码。您跳转到特定的键并从那里开始阅读。因此,您需要保存当前页面的唯一 ID 并跳到下一个页面。或者,您可以预先计算或估计每个页面的起点。
除了明显的效率提升之外,一大好处是避免分页时由于从先前读取的页面中删除行而导致的“缺失行”问题。按键分页时不会发生这种情况,因为键不会改变。
这是一个例子:
让我们假设您有一个名为 的表,TableName
其索引为Id
,并且您希望从最新Id
值开始并向后工作。
你从以下开始:
SELECT TOP (@numRows)
*
FROM TableName
ORDER BY Id DESC;
Run Code Online (Sandbox Code Playgroud)
注意使用
ORDER BY
以确保顺序正确在某些 RDBMS 中,您需要
LIMIT
而不是TOP
客户端将保留最后收到的Id
值(在本例中为最低值)。在下一个请求时,您跳转到该键并继续:
SELECT TOP (@numRows)
*
FROM TableName
WHERE Id < @lastId
ORDER BY Id DESC;
Run Code Online (Sandbox Code Playgroud)
注意使用
<
not<=
如果您想知道,在典型的 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;
Run Code Online (Sandbox Code Playgroud)
这比看起来更有效,因为 SQL Server 可以将其转换为<
两个值的正确值。
s的存在NULL
使事情变得更加复杂。您可能想要单独查询这些行。
小智 8
在非常大的商家网站上,我们使用存储在伪临时表中的 ids 技术组合,并将该表与产品表的行连接起来。
我用一个明显的例子来谈谈。
我们有这样的表格设计:
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);
Run Code Online (Sandbox Code Playgroud)
我们有一个非常大的产品表,称为 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
Run Code Online (Sandbox Code Playgroud)
然后,每当我们需要所需的页面、@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
Run Code Online (Sandbox Code Playgroud)
所有索引都可以完成这项工作!
当然,我们必须定期清除该表,这就是为什么我们有一个计划作业来删除 4 个多小时前生成会话的行:
DELETE FROM S_TEMP.T_PAGINATION_PGN
WHERE PGN_SESSION_DATE < DATEADD(hour, -4, SYSUTCDATETIME());
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
19782 次 |
最近记录: |