blog-post

理解 Manticore 搜索中的分页

分页是任何搜索引擎的重要功能,使用户能够高效地浏览大量结果集。Manticore 搜索提供了几种强大的分页方法,每种方法都有其优缺点。本文探讨了 Manticore 搜索中可用的不同分页选项,并提供实用的 SQL 示例,帮助您实现最适合您用例的解决方案。

目录

  1. 分页简介
  2. 设置测试环境
  3. 传统的基于偏移的分页
  4. 结果集窗口和 max_matches
  5. 基于滚动的分页
  6. 通过 HTTP JSON 的分页
  7. 分页的排序最佳实践
  8. 限制和性能考虑
  9. 带有分页的分面搜索
  10. 实用案例
  11. 结论

分页简介

处理大型数据集时,一次性返回所有匹配的结果是不可行的。分页通过将结果划分为可管理的块或“页面”来解决这个问题。Manticore 搜索提供了多种分页方法,以适应从简单列表导航到无限滚动界面的不同用例。

在本文中,我们将探讨三种主要的分页方法:

  • 传统的基于偏移的分页 ( 第 3 节 )
  • 具有 max_matches 的深度分页 ( 第 4 节 )
  • 基于滚动的分页,以高效浏览大型结果集 ( 第 5 节 )

我们还将讨论它们通过 SQL 和 HTTP JSON 接口的实现、性能考虑以及实际应用。

设置测试环境

在深入分页示例之前,让我们借助 Manticore Load 工具创建一个带有示例数据的测试环境。此工具使生成和加载测试数据以进行基准测试和实验变得简单:

# 创建一个产品表并用 10,000 条包含“智能手机”标题的记录填充它
manticore-load --quiet \
  --batch-size=1000 \
  --threads=4 \
  --total=10000 \
  --drop \
  --init="CREATE TABLE products(title text, description text, price int, category string, rating float, stock int)" \
  --load="INSERT INTO products(id, title, description, price, category, rating, stock) VALUES(<increment>, '<text/3/5> smartphone', '<text/20/50>', <int/10/1000>, '<string/5/10>', <float/1/5>, <int/0/100>)"

输出:
manticore-load 输出

有了这个测试环境,我们现在可以探索不同的分页方法。

传统的基于偏移的分页

Manticore 搜索中最直接的分页方法使用 LIMIT 子句与偏移和计数参数。这种方法对任何与 SQL 数据库打过交道的人都很熟悉。

基本语法

SELECT ... FROM ... [LIMIT [offset,] row_count]
SELECT ... FROM ... [LIMIT row_count][ OFFSET offset]

示例

-- 返回前 5 个结果(页面 1)
SELECT id, title, price, weight() FROM products 
WHERE MATCH('smartphone') 
ORDER BY weight() DESC, price ASC, id ASC
LIMIT 5;

输出:

+------+----------------------------------------------+-------+----------+
| id   | title                                        | price | weight() |
+------+----------------------------------------------+-------+----------+
| 1179 | 弱的轮辐? 落下。智能手机                     |    10 |     1272 |
| 1388 | 3; 她的日子。智能手机                       |    10 |     1272 |
| 1636 | 表现得很糟糕的冬季清理。智能手机           |    10 |     1272 |
| 5628 | 愚蠢的因低而被听到。智能手机               |    10 |     1272 |
| 8561 | 左边的发送。不经常,尝试躺下。智能手机     |    10 |     1272 |
+------+----------------------------------------------+-------+----------+
-- 返回结果 6-10(页面 2)
SELECT id, title, price, weight() FROM products 
WHERE MATCH('smartphone') 
ORDER BY weight() DESC, price ASC, id ASC
LIMIT 5, 5;

输出:

+------+----------------------------------------------+-------+----------+
| id   | title                                        | price | weight() |
+------+----------------------------------------------+-------+----------+
|  246 | 柔软的技术性袭来。智能手机                  |    11 |     1272 |
| 1105 | 脏污的东东!暂时被称为。智能手机            |    11 |     1272 |
| 3293 | 立刻厚重的跑来伤害。智能手机               |    11 |     1272 |
| 3736 | 她错误的本土工作。智能手机                 |    11 |     1272 |
| 6978 | 陈旧的响亮被动的甜美清洁。智能手机        |    11 |     1272 |
+------+----------------------------------------------+-------+----------+
-- 使用单独的 LIMIT 和 OFFSET 的替代语法
SELECT id, title, price, weight() FROM products 
WHERE MATCH('smartphone') 
ORDER BY weight() DESC, price ASC, id ASC
LIMIT 5 OFFSET 10;
-- 返回结果 11-15(页面 3)

输出:

+------+----------------------------------------+-------+----------+
| id   | title                                  | price | weight() |
+------+----------------------------------------+-------+----------+
| 7318 | 她的支出。智能手机                    |    11 |     1272 |
| 8436 | 他的? 感觉工作。不快。智能手机        |    11 |     1272 |
| 9699 | 感觉花费有趣。智能手机                     |    11 |     1272 |
| 2326 | 安静的湿画发现。智能手机                   |    12 |     1272 |
| 4171 | 设置也许新的发送很快。智能手机             |    12 |     1272 |
+------+--------------------------------------------+-------+----------+

这个方法简单易行,适合在初始结果页面之间导航。然而,由于Manticore内部处理查询的方式,对于深分页效率较低。有关这些限制的详细信息,请参见 第8节:限制和性能考虑

结果集窗口和max_matches

默认情况下,Manticore Search将可以返回的匹配数限制为1000。如果您尝试超出此限制进行分页,则查询将导致错误。这个限制可以使用max_matches选项进行调整。

理解max_matches

max_matches选项控制Manticore在搜索时将在RAM中保留多少匹配项。它旨在限制每个查询的内存使用。默认值为1000,对于大多数常见搜索场景来说已足够,但在需要访问更深的结果页面时,您可以增加此值。

-- 增加max_matches以允许更深的分页(结果1001-1005)
SELECT id, title, price FROM products 
WHERE MATCH('智能手机') 
ORDER BY weight() DESC, price ASC, id ASC
LIMIT 1000, 5 
OPTION max_matches=1500;

输出:

+------+-------------------------------------------+-------+
| id   | title                                     | price |
+------+-------------------------------------------+-------+
| 3550 | 开放出售迟到宽阔。智能手机               |   111 |
| 4896 | 增长。写道非常饥饿。智能手机             |   111 |
|  647 | 去见过有。智能手机                       |   112 |
|  883 | 保持失去;有想法。智能手机               |   112 |
| 1774 | 得到!开始生病。走了,狂野。智能手机   |   112 |
+------+-------------------------------------------+-------+
-- 对于非常深的分页(结果5000-5005)
SELECT id, title, price FROM products 
WHERE MATCH('智能手机') 
ORDER BY weight() DESC, price ASC, id ASC
LIMIT 5000, 5 
OPTION max_matches=5500;

输出:

+------+------------------------------------------------+-------+
| id   | title                                          | price |
+------+------------------------------------------------+-------+
| 4894 | 微妙地?内部花费傍晚。智能手机              |   507 |
| 5203 | 糟糕;艺术性从每年。智能手机               |   507 |
| 7446 | 空的可能聪明!全球的。智能手机            |   507 |
| 8053 | 在!丰富的每日不规则。智能手机            |   507 |
| 8055 | 短冷秋;保持身体健康。智能手机            |   507 |
+------+------------------------------------------------+-------+

请记住,增加max_matches会带来内存成本。每个匹配都会消耗内存,因此将此值设置得过高可能会影响服务器性能,尤其是在负载较重的情况下。有关更具内存效率的深分页替代方法,请参见 基于滚动的分页

基于滚动的分页

基于滚动的分页提供了一种高效的方法来在大型结果集中导航。与传统的基于偏移量的分页不同,它使用一个令牌来跟踪当前在结果集中的位置,这有助于有效地在结果的连续页面之间导航。

工作原理

  1. 执行带有排序标准的初始查询(ORDER BY中必须包含id
  2. 检索一个滚动令牌,它封装当前的位置
  3. 在后续查询中使用此令牌以获取下一批结果

示例

-- 带有排序标准的初始查询(ORDER BY中必须包含id)
SELECT id, title, price, weight() FROM products 
WHERE MATCH('智能手机') 
ORDER BY weight() DESC, price ASC, id ASC 
LIMIT 5; SHOW SCROLL\G

输出:

+------+--------------------------------------------+-------+----------+
| id   | title                                      | price | weight() |
+------+--------------------------------------------+-------+----------+
| 1179 | 微弱说话?跌落。智能手机                  |    10 |     1272 |
| 1388 | 3;她的日子。智能手机                     |    10 |     1272 |
| 1636 | 积极不好的冬季清洁。智能手机             |    10 |     1272 |
| 5628 | 愚蠢的从去的听见低。智能手机             |    10 |     1272 |
| 8561 | 左发送。不频繁,尝试放置。智能手机       |    10 |     1272 |
+------+--------------------------------------------+-------+----------+

*************************** 1. 行 ***************************
scroll_token: eyJvcmRlcl9ieV9zdHIiOiJ3ZWlnaHQoKSBERVNDLCBwcmljZSBBU0MsIGlkIEFTQyIsIm9yZGVyX2J5IjpbeyJhdHRyIjoid2VpZ2h0KCkiLCJkZXNjIjp0cnVlLCJ2YWx1ZSI6MTI3MiwidHlwZSI6ImludCJ9LHsiYXR0ciI6InByaWNlIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjoxMCwidHlwZSI6ImludCJ9LHsiYXR0ciI6ImlkIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjo4NTYxLCJ0eXBlIjoiaW50In1dfQ==
-- 使用滚动令牌的后续分页查询
SELECT id, title, price, weight() FROM products 
WHERE MATCH('智能手机') 
ORDER BY weight() DESC, price ASC, id ASC 
LIMIT 5 
OPTION scroll='eyJvcmRlcl9ieV9zdHIiOiJ3ZWlnaHQoKSBERVNDLCBwcmljZSBBU0MsIGlkIEFTQyIsIm9yZGVyX2J5IjpbeyJhdHRyIjoid2VpZ2h0KCkiLCJkZXNjIjp0cnVlLCJ2YWx1ZSI6MTI3MywidHlwZSI6ImludCJ9LHsiYXR0ciI6InByaWNlIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjo1MiwidHlwZSI6ImludCJ9LHsiYXR0ciI6ImlkIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjo0NCwidHlwZSI6ImludCJ9XX0=';

输出:

+------+--------------------------------------------+-------+----------+
| id   | title                                      | price | weight() |
+------+--------------------------------------------+-------+----------+
| 1179 | 微弱说话?跌落。智能手机                  |    10 |     1272 |
| 1388 | 3;她的日子。智能手机                     |    10 |     1272 |
| 1636 | 积极不好的冬季清洁。智能手机             |    10 |     1272 |
| 5628 | 愚蠢的从曾听到低。智能手机      |    10 |     1272 |
| 8561 | 左发送。偶尔,尝试躺下。智能手机 |    10 |     1272 |
+------+----------------------------------------------+-------+----------+

基于滚动的分页特别适合在Web应用程序中实现“加载更多”或无限滚动功能。有关完整的实现示例,请参见 实际使用案例

通过HTTP JSON进行分页

除了SQL,Manticore Search还提供通过HTTP JSON进行分页。这对于Web应用程序或微服务架构特别有用。传统的基于偏移量的分页和基于滚动的分页都可以通过JSON API支持。

通过HTTP JSON进行传统的基于偏移量的分页

HTTP JSON API使用offsetlimit参数来控制分页。这相当于SQL LIMIT offset, count 语法。

基本语法

{
  "table": "table_name",
  "query": { "match": { ... } },
  "limit": number_of_results,
  "offset": starting_position
}

示例

# 第一页(结果1-5)
curl -s 0:9308/search -d '{
  "table": "products",
  "query": { "match": { "*": "智能手机" } },
  "limit": 5,
  "offset": 0,
  "sort": [
    { "_score": "desc" },
    { "price": "asc" },
    { "id": "asc" }
  ]
}'|jq .

输出:

{
  "took": 0,
  "timed_out": false,
  "hits": {
    "total": 10000,
    "total_relation": "eq",
    "hits": [
      {
        "_id": 1179,
        "_score": 1272,
        "_source": {
          "title": "弱的发言?跌倒。智能手机",
          "description": "从未长时间愤怒地被询问关闭!冷的历史上常常情绪化地被带走;社会上被问到经常认为无处可去?仔细?科学上很少。",
          "price": 10,
          "category": "hkadwpwwk",
          "rating": 2.300000,
          "stock": 63
        }
      },
      // ... 更多结果 ...
    ]
  }
}
# 第二页(结果6-10)
curl -s 0:9308/search -d '{
  "table": "products",
  "query": { "match": { "*": "智能手机" } },
  "limit": 5,
  "offset": 5,
  "sort": [
    { "_score": "desc" },
    { "price": "asc" },
    { "id": "asc" }
  ]
}' | jq .

输出:

{
  "took": 0,
  "timed_out": false,
  "hits": {
    "total": 10000,
    "total_relation": "eq",
    "hits": [
      {
        "_id": 246,
        "_score": 1272,
        "_source": {
          "title": "软的技术性被采纳。智能手机",
          "description": "有时这个空短暂通常!被发送。在本地善良!迅速可能偶尔文化上柔和地安静逐渐做到好;里面死寂响亮。",
          "price": 11,
          "category": "wuiymrjdp",
          "rating": 1.600000,
          "stock": 87
        }
      },
      // ... 更多结果 ...
    ]
  }
}

替代语法

HTTP JSON API还支持使用fromsize的替代语法,而不是offsetlimit

{
  "table": "table_name",
  "query": { "match": { ... } },
  "size": number_of_results,
  "from": starting_position
}

这与之前的格式等效,但遵循了Elasticsearch的约定,对某些用户可能更加熟悉。

通过HTTP JSON进行基于滚动的分页

HTTP JSON API还支持基于滚动的分页,以实现更高效的深度分页。这需要设置正确的排序标准并使用滚动令牌。

初始查询

curl -s 0:9308/search -d '{
  "table": "products",
  "query": { "match": { "*": "智能手机" } },
  "limit": 5,
  "track_scores": true,
  "sort": [
    { "_score": "desc" },
    { "id": "asc" }
  ],
  "options": {
    "scroll": true
  }
}' | jq .

输出:

{
  "took": 0,
  "timed_out": false,
  "hits": {
    "total": 10000,
    "total_relation": "eq",
    "hits": [
      {
        "_id": 1,
        "_score": 1272,
        "_source": {
          "title": "不正确的沉重柔软。智能手机",
          "description": "仔细的晚座;放置绘制恐惧;几乎给了1000这个在理解上总是向东假冒,乏味国际发送。在冷;热。",
          "price": 540,
          "category": "ogqejby",
          "rating": 2.800000,
          "stock": 52
        }
      },
      // ... 更多结果 ...
    ]
  },
  "scroll": "eyJvcmRlcl9ieV9zdHIiOiJAd2VpZ2h0IGRlc2MsIGlkIGFzYyIsIm9yZGVyX2J5IjpbeyJhdHRyIjoid2VpZ2h0KCkiLCJkZXNjIjp0cnVlLCJ2YWx1ZSI6MTI3MiwidHlwZSI6ImludCJ9LHsiYXR0ciI6ImlkIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjo1LCJ0eXBlIjoiaW50In1dfQ=="
}

请注意响应中的scroll值,这是一个Base64编码的令牌,表示您在结果集中的当前位置。

随后查询

curl -s 0:9308/search -d '{
  "table": "products",
  "query": { "match": { "*": "智能手机" } },
  "limit": 5,
  "track_scores": true,
  "options": {
    "scroll": "eyJvcmRlcl9ieV9zdHIiOiJAd2VpZ2h0IGRlc2MsIGlkIGFzYyIsIm9yZGVyX2J5IjpbeyJhdHRyIjoid2VpZ2h0KCkiLCJkZXNjIjp0cnVlLCJ2YWx1ZSI6MTI3MiwidHlwZSI6ImludCJ9LHsiYXR0ciI6ImlkIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjo1LCJ0eXBlIjoiaW50In1dfQ=="
  }
}' | jq .

输出:

{
  "took": 0,
  "timed_out": false,
  "hits": {
    "total": 9995,
    "total_relation": "eq",
    "hits": [
      {
        "_id": 6,
        "_score": 1272,
        "_source": {
          "title": "消极的!它变成。智能手机",
          "description": "安静冷漠的夜晚,贫穷的小时薄的神经,非正式工作,100 50特定严肃地带下开始!富有响亮。",
          "price": 602,
          "category": "hjpwt",
          "rating": 2.500000,
          "stock": 47
        }
      },
      // ... 更多结果 ...
    ]
  },
  "scroll": "eyJvcmRlcl9ieV9zdHIiOiJAd2VpZ2h0IGRlc2MsIGlkIGFzYyIsIm9yZGVyX2J5IjpbeyJhdHRyIjoid2VpZ2h0KCkiLCJkZXNjIjp0cnVlLCJ2YWx1ZSI6MTI3MiwidHlwZSI6ImludCJ9LHsiYXR0ciI6ImlkIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjoxMCwidHlwZSI6ImludCJ9XX0="
}

每个响应包括下一个页面的新滚动令牌,允许继续分页。

HTTP JSON分页的最佳实践

  1. 包含排序标准:为了可预测的分页结果,始终包括明确的排序,例如:
    "sort": [
      { "_score": "desc" },
      { "price": "asc" },
      { "id": "asc" }
    ]
    
  2. 正确处理滚动令牌:存储每个响应的滚动令牌,并在下一个请求中使用它。
  3. 使用适当的错误处理:检查错误响应,特别是在深度分页时。
  4. 考虑响应大小:通过仅选择所需字段来控制返回的数据量,使用_source参数:
    "_source": ["title", "price", "rating"]
    
  5. 监控性能:对于非常大的结果集,监控服务器性能,考虑使用基于滚动的分页。

HTTP JSON分页方法遵循与其SQL对应物相同的原则(在 第3节第5节 中描述),但使用JSON语法。这使得它们非常适合现代Web应用程序和REST API。

分页的排序最佳实践

正确的排序对于有效的分页至关重要,特别是对于基于滚动的分页。以下是关键考虑因素:

1. 包含唯一标识符

为了确保一致的分页结果,始终在ORDER BY子句中包含唯一标识符(通常是id字段)。这确保即使多个文档在其他排序标准下具有相同值,分页仍会产生一致的结果。

-- 好:包含id作为平局打破者
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY weight() DESC, price ASC, id ASC;

-- 不好:没有唯一标识符,可能导致不一致的分页
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY price ASC;

2. 包含weight()进行全文搜索

在使用MATCH()进行查询时,始终在排序标准中包含weight(),以确保最相关的结果首先出现:

-- 好:包含weight()进行相关性排序
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY weight() DESC, price ASC, id ASC;

-- 较差:缺少相关性排序
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY price ASC, id ASC;

3. 保持排序顺序一致性

在使用传统的基于偏移量的分页时,始终在所有分页查询中保持相同的排序顺序。在页面之间更改排序顺序可能会导致结果错过或重复。

-- 第一页(正确)
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY weight() DESC, price ASC, id ASC LIMIT 10;

-- 第二页(正确 - 相同的排序顺序)
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY weight() DESC, price ASC, id ASC LIMIT 10, 10;

-- 第二页(不正确 - 不同的排序顺序)
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY rating DESC, id ASC LIMIT 10, 10;

4. 使用确定性排序

对于基于滚动的分页,始终使用确定性的排序标准。应避免使用非确定性函数如RAND(),因为它们会在查询之间产生不一致的结果。

-- 好:确定性排序
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY weight() DESC, price ASC, id ASC;

-- 不好:非确定性排序,将打破滚动分页
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY RAND();

5. 使用ID的排序方向

在使用id字段作为排序的平局打破者时,可以使用ASC或DESC顺序,但必须一致应用:

-- 这两种方法对于滚动分页都是有效的
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY weight() DESC, price ASC, id ASC;
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY weight() DESC, price ASC, id DESC;

6. 优化排序性能

为了更好的排序性能,考虑使用sort_method选项:

-- 对于预排序的数据(例如,通过id)
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY id ASC 
LIMIT 5 OPTION sort_method=kbuffer;

-- 对于其他排序标准
SELECT * FROM products WHERE MATCH('smartphone') ORDER BY weight() DESC, price ASC, id ASC 
LIMIT 5 OPTION sort_method=pq;

您选择的排序方法可以显著影响分页性能和一致性。例如,确定性排序对基于滚动的分页至关重要,而包含唯一标识符对于所有分页方法中的一致结果至关重要。有关排序如何影响性能的更多细节,请参见 限制和性能考虑

限制和性能考虑

了解不同分页方法的限制将帮助您为特定用例选择正确的方法。

基于偏移量的分页限制

  1. 大型偏移量时性能下降:随着偏移量值的增加,查询性能下降。这是因为Manticore仍然需要处理所有记录,直到偏移量值,然后返回请求的页面。
  2. max_matches约束:默认情况下,您只能通过前1000个结果进行分页。要超过此限制,您必须增加max_matches,这会增加内存使用。
  3. 内存使用:较大的max_matches值每个查询需要更多的RAM,这可能在负载较重时影响服务器性能。
  4. 不保证一致的结果:如果在页面请求之间添加或删除了文档,您可能会看到重复或错过结果。

对于深度分页场景或实现“加载更多”功能,请考虑使用 Scroll-Based Pagination 中描述的内容,见 第 5 节

基于滚动的分页限制

  1. 需要排序条件中的 ID:您必须在 ORDER BY 子句中包含 id 字段,这可能与您期望的排序逻辑不 always 一致。
  2. 令牌管理:您需要在请求之间存储和管理滚动令牌,这给您的应用程序增加了复杂性。
  3. 无一致性保证:基于滚动的分页不保证时间点一致性。如果在请求之间添加、删除或修改文档,结果仍可能受到影响。
  4. 无随机访问:与基于偏移量的分页不同,您不能直接跳转到特定页面;必须按顺序导航。

带分页的多面搜索

多面搜索允许用户使用多个维度对搜索结果进行过滤和导航。与分页结合时,理解分页如何影响面结果很重要。

-- 带分页的多面搜索(第一页)
SELECT id, title, price, weight()
FROM products 
WHERE MATCH('smartphone') 
ORDER BY weight() DESC, price ASC, id ASC
LIMIT 0, 5
FACET category ORDER BY COUNT(*) DESC;

输出:

+------+----------------------------------------------+-------+----------+
| id   | title                                        | price | weight() |
+------+----------------------------------------------+-------+----------+
| 1179 | 弱轮辐?坠落。智能手机                     |    10 |     1272 |
| 1388 | 3;她的日子。智能手机                      |    10 |     1272 |
| 1636 | 确实糟糕的冬天清洁。智能手机               |    10 |     1272 |
| 5628 | 愚蠢的从去过低。智能手机                   |    10 |     1272 |
| 8561 | 左边发送。很少,尝试躺下。智能手机         |    10 |     1272 |
+------+----------------------------------------------+-------+----------+
+-----------+----------+
| category  | count(*) |
+-----------+----------+
| ogqejby   |        1 |
| unmfujgqr |        1 |
| ttefm     |        1 |
| hceihdy   |        1 |
| sicjr     |        1 |
| hjpwt     |        1 |
| tvfqyj    |        1 |
| mvdjhbexo |        1 |
| scayuo    |        1 |
| esmlh     |        1 |
| fvbhplj   |        1 |
| lcphmiqmv |        1 |
| lnjfhb    |        1 |
| qexfdulub |        1 |
| tbswa     |        1 |
| eekarf    |        1 |
| airjuod   |        1 |
| ozkbuvgj  |        1 |
| yafbhr    |        1 |
| duccr     |        1 |
+-----------+----------+
-- 带分页的多面搜索(第二页)
SELECT id, title, price, weight()
FROM products 
WHERE MATCH('smartphone') 
ORDER BY weight() DESC, price ASC, id ASC
LIMIT 5, 5
FACET category ORDER BY COUNT(*) DESC;

输出:

+------+-----------------------------------------------------------+-------+----------+
| id   | title                                                     | price | weight() |
+------+-----------------------------------------------------------+-------+----------+
|  246 | 软性技术采取。智能手机                                 |    11 |     1272 |
| 1105 | 脏的上是!临时叫。智能手机                             |    11 |     1272 |
| 3293 | 立刻变厚了那伤。智能手机                               |    11 |     1272 |
| 3736 | 工作她错过当地。智能手机                               |    11 |     1272 |
| 6978 | 陈旧响亮被动甜美清洁。智能手机                         |    11 |     1272 |
+------+-----------------------------------------------------------+-------+----------+
+-----------+----------+
| category  | count(*) |
+-----------+----------+
| ogqejby   |        1 |
| unmfujgqr |        1 |
| ttefm     |        1 |
| hceihdy   |        1 |
| sicjr     |        1 |
| hjpwt     |        1 |
| tvfqyj    |        1 |
| mvdjhbexo |        1 |
| scayuo    |        1 |
| esmlh     |        1 |
| fvbhplj   |        1 |
| lcphmiqmv |        1 |
| lnjfhb    |        1 |
| qexfdulub |        1 |
| tbswa     |        1 |
| eekarf    |        1 |
| airjuod   |        1 |
| ozkbuvgj  |        1 |
| yafbhr    |        1 |
| duccr     |        1 |
+-----------+----------+

重要说明: 面结果是基于与查询匹配的整个结果集计算的,而不仅仅是分页部分。这意味着无论您查看哪个页面,面计数都代表整个匹配文档的分布。LIMIT 子句仅影响主要结果集中的返回文档,而不影响面计算。如您所见,上述示例中,两个页面的面结果是相同的,确认它们是从整个结果集计算的。

面结果中的分页

除分页主要搜索结果外,Manticore Search 还允许您对面结果本身进行分页。这在处理大量面值时尤其有用,因为显示所有面值可能不实际。

您可以在 FACET 语句中使用 LIMIT 子句来控制返回的面值数量。

-- 仅获取按计数排序的前 5 个类别
SELECT id, title FROM products 
WHERE MATCH('smartphone') 
LIMIT 3 
FACET category ORDER BY COUNT(*) DESC, category asc LIMIT 0,5;

输出:

+------+---------------------------------------------+
| id   | title                                       |
+------+---------------------------------------------+
|    1 | 错误地沉重柔软。智能手机                     |
|    2 | 知道的制造害怕愚蠢。智能手机               |
|    3 | 糟糕的春天傍晚驱动年轻。智能手机           |
+------+---------------------------------------------+
+------------+----------+
| category   | count(*) |
+------------+----------+
| aaaabzwfzn |        1 |
| aabswb     |        1 |
| aacla      |        1 |
| aaejmtubv  |        1 |
| aaethytj   |        1 |
+------------+----------+

您可以为同一查询中的不同方面提供不同的分页参数:

-- 多个方面具有不同的分页参数
SELECT id, title FROM products 
WHERE MATCH('smartphone') 
LIMIT 3 
FACET category ORDER BY COUNT(*) DESC, category asc LIMIT 0,5 
FACET category ORDER BY category ASC LIMIT 3,5 
FACET price ORDER BY FACET() ASC LIMIT 5;

输出:

+------+---------------------------------------------+
| id   | title                                       |
+------+---------------------------------------------+
|    1 | 错误地沉重的柔软。智能手机                  |
|    2 | 知道让人害怕愚蠢。智能手机                 |
|    3 | 糟糕的春季晚上驱动的年轻。智能手机         |
+------+---------------------------------------------+
+------------+----------+
| category   | count(*) |
+------------+----------+
| aaaabzwfzn |        1 |
| aabswb     |        1 |
| aacla      |        1 |
| aaejmtubv  |        1 |
| aaethytj   |        1 |
+------------+----------+
+------------+----------+
| category   | count(*) |
+------------+----------+
| aaejmtubv  |        1 |
| aaethytj   |        1 |
| aaktjgaa   |        1 |
| aalfwcvwil |        1 |
| aaqmumofe  |        1 |
+------------+----------+
+-------+----------+
| price | count(*) |
+-------+----------+
|    10 |        5 |
|    11 |        8 |
|    12 |       10 |
|    13 |        9 |
|    14 |        8 |
+-------+----------+

此示例演示了几种方面分页技术:

  1. 第一个方面返回按计数排序的前5个类别(偏移量0,限制5)
  2. 第二个方面按字母顺序返回类别,跳过前3个(偏移量3,限制5)
  3. 第三个方面返回按升序排列的前5个价格点

方面分页特别适用于:

  • 处理大量方面值(例如,成千上万的产品类别)
  • 实现“显示更多”功能以查看更多方面值
  • 创建动态过滤接口,首先显示最受欢迎的过滤器
  • 通过限制返回的方面值的数量来优化性能

注意: 与主结果分页不同,主结果分页在深偏移时具有性能影响,方面分页通常在大偏移时仍然有效,因为方面计算发生在文档匹配阶段之后。

通过HTTP JSON进行方面分页

使用HTTP JSON接口时,也可以实现方面分页。您可以在聚合规范中使用size参数限制返回的方面值数量:

{
  "table": "products",
  "query": { "match": { "*": "smartphone" } },
  "limit": 3,
  "aggs": {
    "category_counts": {
      "terms": {
        "field": "category",
        "size": 5
      }
    }
  }
}

此示例限制类别方面结果为前5个值。

重要: 与SQL方面不同,HTTP JSON接口目前仅支持使用size参数限制方面值的数量。不支持在方面内进行分页的OFFSET参数。您只能指定返回多少结果,而不能指定跳过哪些结果。

有关通过HTTP JSON进行方面分页的更多详细信息,请参见 Manticore 搜索文档

实际用例

带高亮的分页

-- 带高亮的分页
SELECT id, title, price, weight(),
       HIGHLIGHT({limit=100}) as highlight
FROM products 
WHERE MATCH('smartphone') 
ORDER BY weight() DESC, price ASC, id ASC
LIMIT 5, 5;

输出:

+------+----------------------------------------------+-------+----------+-----------------------------------------------------+
| id   | title                                        | price | weight() | highlight                                           |
+------+----------------------------------------------+-------+----------+-----------------------------------------------------+
|  246 | 软的技术上已采取。智能手机                 |    11 |     1272 | 软的技术上已采取。<b>智能手机</b>                   |
| 1105 | 肮脏的事情!暂时称之为。智能手机           |    11 |     1272 | 肮脏的事情!暂时称之为。<b>智能手机</b>              |
| 3293 | 瞬间变厚了,伤害了。智能手机               |    11 |     1272 | 瞬间变厚了,伤害了。<b>智能手机</b>                  |
| 3736 | 工作她出了错,局部地方。智能手机          |    11 |     1272 | 工作她出了错,局部地方。<b>智能手机</b>              |
| 6978 | 陈旧的声音被动甜美干净。智能手机          |    11 |     1272 | 陈旧的声音被动甜美干净。<b>智能手机</b>              |
+------+----------------------------------------------+-------+----------+-----------------------------------------------------+

实现“加载更多”按钮与滚动分页

基于滚动的分页非常适合实现“加载更多”功能,因为它提供了高效的方式在大型结果集之间导航。以下是如何实现它:

-- 初始查询与SHOW SCROLL以获取滚动令牌
SELECT id, title, price, weight() FROM products 
WHERE MATCH('smartphone') 
ORDER BY weight() DESC, price ASC, id ASC 
LIMIT 5; SHOW SCROLL\G

输出:

+------+------------------------------------------------+-------+----------+
| id   | title                                          | price | weight() |
+------+------------------------------------------------+-------+----------+
| 1179 | 弱的说?摔倒。智能手机                        |    10 |     1272 |
| 1388 | 3;她的日子。智能手机                        |    10 |     1272 |
| 1636 | 积极的糟糕的冬季清洁。智能手机              |    10 |     1272 |
| 5628 | 愚蠢地从听说低。智能手机                    |    10 |     1272 |
| 8561 | 左边寄送。偶尔,尝试躺下。智能手机         |    10 |     1272 |
+------+------------------------------------------------+-------+----------+

*************************** 1. 行 ***************************
scroll_token: eyJvcmRlcl9ieV9zdHIiOiJ3ZWlnaHQoKSBERVNDLCBwcmljZSBBU0MsIGlkIEFTQyIsIm9yZGVyX2J5IjpbeyJhdHRyIjoid2VpZ2h0KCkiLCJkZXNjIjp0cnVlLCJ2YWx1ZSI6MTI3MiwidHlwZSI6ImludCJ9LHsiYXR0ciI6InByaWNlIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjoxMCwidHlwZSI6ImludCJ9LHsiYXR0ciI6ImlkIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjo4NTYxLCJ0eXBlIjoiaW50In1dfQ==
1 row in set (0.00 sec)
-- 当用户点击“加载更多”时,使用滚动令牌获取下一批
SELECT id, title, price, weight() FROM products 
WHERE MATCH('smartphone') 
LIMIT 5 
OPTION scroll='eyJvcmRlcl9ieV9zdHIiOiJ3ZWlnaHQoKSBERVNDLCBwcmljZSBBU0MsIGlkIEFTQyIsIm9yZGVyX2J5IjpbeyJhdHRyIjoid2VpZ2h0KCkiLCJkZXNjIjp0cnVlLCJ2YWx1ZSI6MTI3MiwidHlwZSI6ImludCJ9LHsiYXR0ciI6InByaWNlIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjoxMCwidHlwZSI6ImludCJ9LHsiYXR0ciI6ImlkIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjo4NTYxLCJ0eXBlIjoiaW50In1dfQ=='; SHOW SCROLL\G

输出:

+------+---------------------------------------------+-------+----------+
| id   | title                                       | price | weight() |
+------+---------------------------------------------+-------+----------+
|  246 | 软件技术上占用。智能手机                      |    11 |     1272 |
| 1105 | 肮脏时叫。智能手机                           |    11 |     1272 |
| 3293 | 立即变厚跑得痛。智能手机                     |    11 |     1272 |
| 3736 | 本地工作错了。智能手机                       |    11 |     1272 |
| 6978 | 陈旧的声音消极甜美干净。智能手机             |    11 |     1272 |
+------+---------------------------------------------+-------+----------+

*************************** 1. row ***************************
scroll_token: eyJvcmRlcl9ieV9zdHIiOiJ3ZWlnaHQoKSBERVNDLCBwcmljZSBBU0MsIGlkIEFTQyIsIm9yZGVyX2J5IjpbeyJhdHRyIjoid2VpZ2h0KCkiLCJkZXNjIjp0cnVlLCJ2YWx1ZSI6MTI3MiwidHlwZSI6ImludCJ9LHsiYXR0ciI6InByaWNlIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjoxMSwidHlwZSI6ImludCJ9LHsiYXR0ciI6ImlkIiwiZGVzYyI6ZmFsc2UsInZhbHVlIjo2OTc4LCJ0eXBlIjoiaW50In1dfQ==

对于每次随后的“加载更多”点击,您将使用上一个查询返回的新滚动令牌。这种方法相对于传统的基于偏移的分页有几个优点:

  1. 更好的性能:深度分页没有性能下降
  2. 没有 max_matches 限制:您可以高效地逐页浏览数百万条结果
  3. 顺序访问:高效地浏览连续页面的结果

实现提示:

  • 将滚动令牌存储在您的应用状态中(客户端或服务器端)
  • 当用户点击“加载更多”时,在每个后续请求中包含令牌
  • 始终使用最新的令牌,因为每个令牌表示结果集中的当前位
  • 记住,滚动令牌是临时的,并最终会过期

这种实施方法解决了许多传统基于偏移的分页所述的限制,特别是在深度分页场景中。

结论

Manticore Search 提供了灵活的分页选项,以满足不同的用例。传统的基于偏移的分页适用于有限页面导航的简单界面,而基于滚动的分页在深度分页场景(如无限滚动或数据导出)中提供更好的性能。

在 Manticore Search 中实施分页时:

  1. 对于浅层分页(前几页):使用一致排序的传统基于偏移的分页。
  2. 对于深层分页(许多页面或大数据集):使用基于滚动的分页,并使用适当的基于 id 的排序标准。
  3. 对于一致结果:在您的 ORDER BY 子句中始终包含 id 字段,并在查询中保持一致的排序标准。
  4. 对于全文搜索:在您的排序标准中包含 weight()_score,以确保最相关的结果首先出现。

通过理解每种方法的优缺点和最佳实践,您可以为特定应用需求实施最有效的分页策略。请记住,最佳的分页方法取决于您的用例、数据量和用户体验要求。

安装Manticore Search

安装Manticore Search