如何解决使用分页优化 SQL 查询
我有一个针对 SQL Server 数据库运行的查询,执行时间超过 10 秒。被查询的表有超过 1400 万行。
我想按给定的 Text
按日期顺序显示 Notes
表中的 ServiceUserId
列。可能有数千个条目,因此我想将返回值限制在可管理的级别。
SELECT Text
FROM
(SELECT
ROW_NUMBER() OVER (ORDER BY [DateDone]) AS RowNum,Text
FROM
Notes
WHERE
ServiceUserId = '6D33B91A-1C1D-4C99-998A-4A6B0CC0A6C2') AS RowConstrainedResult
WHERE
RowNum >= 40 AND RowNum < 60
ORDER BY
RowNum
下面是上述查询的执行计划。
- 非聚集索引 -
ServiceUserId
和DateDone
列上按升序排列的非聚集索引。 - 键查找 - 表的主键,即
NoteId
如果我第二次运行相同的查询但行号不同,那么我会以毫秒为单位得到响应,我假设来自缓存的执行计划。不过,针对不同的 ServiceUserId
运行的查询将需要大约 10 秒的时间。
有关如何加快此查询速度的任何建议?
解决方法
You should look into Keyset Pagination.
它的性能远高于行集分页。
它与它的根本区别在于,它不是引用特定的行号块,而是引用起点来查找索引键。
它快得多的原因是您不关心特定键之前有多少行,您只需寻找一个键并向前(或向后)移动。
假设您按单个 ServiceUserId
过滤,按 DateDone
排序。你需要一个如下的索引(如果它太大,你可以省略 INCLUDE
,它不会改变数学):
create index IX_DateDone on Notes (ServiceUserId,DateDone) INCLUDE (TEXT);
现在,当你选择一些行时,不要给出开始和结束的行号,而是给出开始键:
SELECT TOP (20)
Text,DateDone
FROM
Notes
WHERE
ServiceUserId = '6D33B91A-1C1D-4C99-998A-4A6B0CC0A6C2'
AND DateDone > @startingDate
ORDER BY
DateDone;
在下一次运行中,您传递您收到的最后一个 DateDone
值。这会让你获得下一批。
一个小缺点是你不能跳转页面。然而,用户想要跳转到第 327 页的情况比某些人想象的要少得多(从 UI 角度来看)。所以没关系。
键必须是唯一的。如果它不是唯一的,你就不能精确地寻找下一行。如果你需要使用额外的列来保证唯一性,它会变得有点复杂:
WITH NotesFiltered AS
(
SELECT * FROM Notes
WHERE
ServiceUserId = '6D33B91A-1C1D-4C99-998A-4A6B0CC0A6C2'
)
SELECT TOP (20)
Text,DateDone
FROM (
SELECT
Text,DateDone,0 AS ordering
FROM NotesFiltered
WHERE
DateDone = @startingDate AND NoteId > @startingNoteId
UNION ALL
SELECT
Text,1 AS ordering
FROM NotesFiltered
WHERE
DateDone > @startingDate
) n
ORDER BY
ordering,NoteId;
旁注
在支持行值比较的 RDBMS 中,多列示例可以通过编写以下代码简化回原始代码:
WHERE (DateDone,NoteId) > (@startingDate,@startingNoteId)
遗憾的是 SQL Server 目前不支持此功能。
请为Azure Feedback request for this
我建议使用 order by offset fetch
:
它从第 x 行开始,然后获取下一行 z,可以参数化
SELECT
Text
FROM
Notes
WHERE
ServiceUserId = '6D33B91A-1C1D-4C99-998A-4A6B0CC0A6C2'
Order by DateDone
OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY
还要确保你有合适的“DateDone”索引,如果你还没有,可以把它包含在你已经在“Notes”上的索引中
您可能需要在索引中包含文本列:
create index IX_DateDone on Notes(DateDone) INCLUDE (TEXT,ServiceUserId)
但是请注意,在索引中添加如此大的列会影响您的插入/更新效率,当然它需要磁盘空间
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。