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