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