Web 应用通常会根据向用户显示的数据对其进行分页。最终用户会收到一页结果,当他们转到下一页时,系统会检索并显示下一批结果。本页介绍了如何在 Spanner 中执行全文搜索时向搜索结果添加分页。
概览
在 Spanner 中,您可以通过以下两种方式实现分页查询: 基于键的分页(推荐)和 基于偏移量的分页。
基于键的分页是一种检索搜索结果的方法,可将搜索结果分成更小、更易于管理的部分,同时确保各个请求的结果一致。唯一 网页最后一条结果的标识符(“键”)用作参考 即可提取下一组结果。
Spanner 通常建议使用基于键的分页。虽然基于偏移量的分页更易于实现,但它有两个重大缺点:
- 查询费用更高:基于偏移的分页会反复检索 会舍弃相同的结果,从而导致成本增加并降低 性能
- 结果不一致:在分页查询中,每个网页通常会在不同的读取时间戳处检索。例如,第一个网页可能 另一个则来自下午 1:10 的查询。这意味着 不同查询之间的搜索结果可能会发生变化,从而导致不一致 结果。
另一方面,基于键的分页使用网页最后一条结果中的唯一标识符(键)提取下一组结果。这样可以确保 实现高效检索并获得一致的结果,即使底层数据发生变化也无妨。
为了确保网页结果的稳定性,应用可以在同一时间戳发出针对不同网页的所有查询。不过,如果查询超出版本保留期限(默认为 1 小时),则可能会失败。例如,如果 version_gc
为
一个小时,最终用户在下午 1 点获取第一批结果并点击下一步
下午 3 点。
使用基于键的分页
基于键的分页会记住前一页面的最后一项,并将其用作
用作下一个页面查询的起点。为此,查询必须返回
ORDER BY
子句中指定的列,并限制行数
使用 LIMIT
。
为了实现基于键的分页,查询必须按某种严格的总排序对结果进行排序。若要获得此类排名,最简单的方法是选择任何总订单数,然后根据需要添加平分排名列。在大多数情况下,总顺序是搜索索引排序 顺序,并且唯一的列组合是基表主键。
使用 Albums
示例架构,对于第一页,查询如下所示:
SELECT AlbumId, ReleaseTimestamp
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")
ORDER BY ReleaseTimestamp DESC, AlbumId
LIMIT 10;
AlbumId
是决定性因素,因为 ReleaseTimestamp
不是键。那里
可能是两个具有相同 ReleaseTimestamp
值的不同影集。
如需继续,应用会再次运行相同的查询,但会使用限制上一个页面结果的 WHERE
子句。附加条件
需要考虑键方向(升序与降序)、连带符
以及可为 null 的列的 NULL 值的顺序。
在我们的示例中,AlbumId
是唯一的键列(按升序排列),且不能为 NULL,因此条件如下所示:
SELECT AlbumId, ReleaseTimestamp
FROM Albums
WHERE (ReleaseTimestamp < @last_page_release_timestamp
OR (ReleaseTimestamp = @last_page_release_timestamp
AND AlbumId > @last_page_album_id))
AND SEARCH(AlbumTitle_Tokens, @p)
ORDER BY ReleaseTimestamp DESC, AlbumId ASC
LIMIT @page_size;
Spanner 会将此类条件解释为可跳转。这意味着,Spanner 不会读取要滤除的文档的索引。这种优化使得基于键的分页更有效率 和基于偏移量的分页
使用基于偏移的分页
基于偏移量的分页功能利用 SQL 查询的 LIMIT
和 OFFSET
子句来模拟页面。LIMIT
值表示每页的结果数。第一页的 OFFSET
值设为 0,第二页的页面大小设为 0
将第三页的页面大小翻倍
例如,以下查询会提取第 3 页(页面大小为 50):
SELECT AlbumId
FROM Albums
WHERE SEARCH(AlbumTitle_Tokens, "fifth symphony")
ORDER BY ReleaseTimestamp DESC, AlbumId
LIMIT 50 OFFSET 100;
使用说明:
- 强烈建议使用
ORDER BY
子句,以确保页面之间的排序一致。 - 在生产查询中,使用查询参数(而非常量)指定
LIMIT
和OFFSET
,以提高查询缓存效率。有关 请参阅 查询参数。
后续步骤
- 了解如何对搜索结果进行排名。
- 了解如何执行子字符串搜索。
- 了解如何混合全文和非文本查询。
- 了解如何搜索多个列。