主页 > 人工智能  > 

【Elasticsearch】分页查询

【Elasticsearch】分页查询

在 Elasticsearch 中,分页技术是处理大量搜索结果时的关键功能,尤其在需要优化性能或处理深度分页时。以下是三种主要的分页方法: from/size 、 scroll  API 和  search_after ,以及它们的详细对比和适用场景。 ---

1.`from/size`分页 原理 `from/size`是最简单的分页方法,通过指定`from`和`size`参数来跳过指定数量的文档并返回指定数量的结果。 实现细节

• `from`:跳过的文档数量。

• `size`:每页返回的文档数量。 示例 请求:

```json GET /index_name/_search {   "from": 0,   "size": 10,   "query": {     "match_all": {}   } } ```

响应:

```json {   "took": 1,   "timed_out": false,   "_shards": {     "total": 1,     "successful": 1,     "skipped": 0,     "failed": 0   },   "hits": {     "total": {       "value": 100,       "relation": "eq"     },     "max_score": 1.0,     "hits": [       {         "_index": "index_name",         "_type": "_doc",         "_id": "1",         "_score": 1.0,         "_source": {           "field1": "value1",           "field2": "value2"         }       },       // 其他文档...     ]   } } ```

优点

• 简单易用,适合用户界面分页。

• 支持随机访问任意页。 缺点

• 性能随`from`值增大而下降。

• 默认限制为 10,000 条结果。 适用场景

• 结果数量较少的场景。

• 用户界面分页。 ---

2.`scroll`API 原理 `scroll`API 通过创建一个快照(snapshot)来保持搜索上下文,允许遍历大量数据。 实现细节

1. 初始化搜索上下文:首次请求返回初始结果和`_scroll_id`。

2. 滚动请求:使用`_scroll_id`获取更多结果。

3. 清理上下文:完成滚动后需清理上下文。 示例 初始化请求:

```json POST /index_name/_search?scroll=1m {   "size": 100,   "query": {     "match_all": {}   } } ```

响应:

```json {   "_scroll_id": "abc123...",   "took": 1,   "timed_out": false,   "_shards": {     "total": 1,     "successful": 1,     "failed": 0   },   "hits": {     "total": {       "value": 1000,       "relation": "eq"     },     "hits": [       {         "_index": "index_name",         "_type": "_doc",         "_id": "1",         "_source": {           "field1": "value1",           "field2": "value2"         }       },       // 其他文档...     ]   } } ```

滚动请求:

```json POST /_search/scroll {   "scroll": "1m",   "scroll_id": "abc123..." } ```

优点

• 处理大量数据。

• 不受 10,000 条结果限制。

缺点

• 需要更多资源来维护搜索上下文。

• 数据状态固定,不会反映后续更改。

适用场景

• 数据导出。

• 批量处理。

---

3.`search_after`分页

原理 `search_after`通过指定上一页最后一条文档的排序值来获取下一页结果。

实现细节

• 需要明确的排序字段。

• 每次请求基于上一页的排序值进行分页。

示例 初始查询:

```json GET /index_name/_search {   "size": 10,   "query": {     "match_all": {}   },   "sort": [     {"price": "desc"},     {"created_at": "asc"}   ] } ```

响应:

```json {   "took": 1,   "timed_out": false,   "_shards": {     "total": 1,     "successful": 1,     "failed": 0   },   "hits": {     "total": {       "value": 100,       "relation": "eq"     },     "hits": [       {         "_index": "index_name",         "_type": "_doc",         "_id": "1",         "_sort": [129.99, "2023-10-23T12:00:00Z"],         "_source": {           "price": 129.99,           "created_at": "2023-10-23T12:00:00Z"         }       },       // 其他文档...     ]   } } ```

下一页查询:

```json GET /index_name/_search {   "size": 10,   "query": {     "match_all": {}   },   "sort": [     {"price": "desc"},     {"created_at": "asc"}   ],   "search_after": [129.99, "2023-10-23T12:00:00Z"] } ```

优点

• 高效,适合深度分页。

• 每次请求独立,实时反映数据变化。

缺点

• 需要排序字段,否则无法使用。

• 数据变化可能导致结果不一致。

适用场景

• 按排序顺序分页。

• 深度分页。

• 实时性要求高的场景。

---

4.点-in-time(PIT)+`search_after`

原理 PIT 创建一个数据一致性视图,结合`search_after`实现稳定分页。

实现细节

1. 创建 PIT:获取 PIT ID。

2. 首次查询:使用 PIT ID 和排序字段。

3. 后续查询:更新`search_after`参数,保持 PIT ID。

示例 创建 PIT:

```json POST /index_name/_pit?keep_alive=1m ```

响应:

```json {   "id": "pit_id_123..." } ```

首次查询:

```json GET /_search {   "size": 10,   "query": {     "match_all": {}   },   "sort": [     {"@timestamp": "desc"},     {"_id": "asc"}   ],   "pit": {     "id": "pit_id_123..."   } } ```

后续查询:

```json GET /_search {   "size": 10,   "query": {     "match_all": {}   },   "sort": [     {"@timestamp": "desc"},     {"_id": "asc"}   ],   "search_after": [1630000000000, "doc_id_123"],   "pit": {     "id": "pit_id_123...",     "keep_alive": "1m"   } } ```

优点

• 数据一致性高。

• 结合`search_after`,适合深度分页。

缺点

• 资源消耗高。

• 需要管理 PIT 生命周期。

适用场景

• 需要数据一致性的深度分页。

---

总结对比

 分页方法 优点 缺点 适用场景                                       

 `from/size` - 简单易用<br>- 支持随机访问任意页 - 性能随 `from` 增大而下降<br>- 默认限制为 10,000 条结果<br>- 不适合深度分页 - 用户界面分页<br>- 结果数量较少的场景          

 `scroll` API - 处理大量数据<br>- 不受 10,000 条结果限制<br>- 适合批量处理 - 资源消耗高(维护搜索上下文)<br>- 数据状态固定(不会反映后续更改)<br>- 需要显式清理上下文 - 数据导出<br>- 批量处理<br>- 不需要实时性的场景  

 `search_after` - 高效(适合深度分页)<br>- 每次请求独立<br>- 实时反映数据变化 - 需要明确的排序字段<br>- 数据变化可能导致结果不一致<br>- 实现逻辑相对复杂 - 按排序顺序分页<br>- 深度分页<br>- 实时性要求高的场景  

 PIT + `search_after` - 数据一致性高<br>- 结合 `search_after`,适合深度分页 - 资源消耗高(维护 PIT 上下文)<br>- 需要管理 PIT 生命周期<br>- 实现复杂度高 - 需要数据一致性的深度分页<br>- 实时性要求高的场景  

---

详细解析与适用场景

1.`from/size`分页

优点:

• 简单易用:适合简单的分页需求,如用户界面分页。

• 支持随机访问:可以直接跳到任意页。

缺点:

• 性能问题:随着`from`值增大,性能会显著下降。

• 深度分页限制:默认情况下,`from + size`不能超过`index.max_result_window`(默认为 10,000)。

适用场景:

• 用户界面分页:适合前端分页需求,如网页或移动应用中的分页。

• 结果数量较少:适合分页逻辑简单且结果数量较少的场景。

示例响应:

```json

{

  "took": 1,

  "timed_out": false,

  "_shards": {

    "total": 1,

    "successful": 1,

    "skipped": 0,

    "failed": 0

  },

  "hits": {

    "total": {

      "value": 100,

      "relation": "eq"

    },

    "max_score": 1.0,

    "hits": [

      {

        "_index": "index_name",

        "_type": "_doc",

        "_id": "1",

        "_score": 1.0,

        "_source": {

          "field1": "value1",

          "field2": "value2"

        }

      },

      // 其他文档...

    ]

  }

}

```

---

2.`scroll`API

优点:

• 处理大量数据:不受 10,000 条结果限制。

• 高效:滚动请求仅加载下一批结果。

缺点:

• 资源消耗:滚动上下文会占用额外的内存和资源。

• 数据一致性:滚动请求返回的是初始搜索时的数据状态。

• 需要清理:必须在完成滚动后显式清理上下文。

适用场景:

• 数据导出:适合将大量数据导出到其他系统。

• 批量处理:适合需要处理大量数据的场景,如日志分析或数据迁移。

示例响应:

```json

{

  "_scroll_id": "abc123...",

  "took": 1,

  "timed_out": false,

  "_shards": {

    "total": 1,

    "successful": 1,

    "failed": 0

  },

  "hits": {

    "total": {

      "value": 1000,

      "relation": "eq"

    },

    "hits": [

      {

        "_index": "index_name",

        "_type": "_doc",

        "_id": "1",

        "_source": {

          "field1": "value1",

          "field2": "value2"

        }

      },

      // 其他文档...

    ]

  }

}

```

---

3.`search_after`分页

优点:

• 高效:不需要扫描所有前序文档。

• 深度分页:适合深度分页。

• 实时性:每次请求都是独立的,可以反映最新的数据状态。

缺点:

• 依赖排序:需要明确的排序字段。

• 结果一致性:如果数据在分页过程中发生变化,可能会导致结果不一致。

• 复杂性:需要管理排序值和`search_after`参数。

适用场景:

• 按排序顺序分页:适合需要按特定顺序分页的场景。

• 深度分页:适合需要分页浏览大量结果的场景。

• 实时性要求高:适合需要实时反映数据变化的场景。

示例响应:

```json

{

  "took": 1,

  "timed_out": false,

  "_shards": {

    "total": 1,

    "successful": 1,

    "skipped": 0,

    "failed": 0

  },

  "hits": {

    "total": {

      "value": 100,

      "relation": "eq"

    },

    "hits": [

      {

        "_index": "index_name",

        "_type": "_doc",

        "_id": "1",

        "_score": null,

        "_source": {

          "price": 129.99,

          "created_at": "2023-10-23T12:00:00Z"

        },

        "sort": [129.99, "2023-10-23T12:00:00Z"]

      },

      // 其他文档...

    ]

  }

}

```

---

4.点-in-time(PIT)+`search_after`

优点:

• 数据一致性:PIT 保持了搜索上下文,确保分页过程中数据状态一致。

• 高效:结合`search_after`,可以高效地分页浏览大量数据。

缺点:

• 资源消耗:PIT 会占用额外的内存和资源。

• 复杂性:需要管理 PIT 生命周期和上下文。

适用场景:

• 需要数据一致性:适合在分页过程中需要保持数据状态一致的场景。

• 深度分页:适合需要分页浏览大量结果的场景。

• 实时性要求高:适合需要实时反映数据变化的场景。

示例响应:

```json

{

  "id": "pit_id_123..."

}

```

首次查询响应:

 

```json

{

  "took": 1,

  "timed_out": false,

  "_shards": {

    "total": 1,

    "successful": 1,

    "skipped": 0,

    "failed": 0

  },

  "hits": {

    "total": {

      "value": 100,

      "relation": "eq"

    },

    "hits": [

      {

        "_index": "index_name",

        "_type": "_doc",

        "_id": "1",

        "_score": null,

        "_source": {

          "@timestamp": "2023-10-23T12:00:00Z",

          "field1": "value1"

        },

        "sort": [1630000000000, "doc_id_123"]

      },

      // 其他文档...

    ]

  }

}

```

---

总结

选择哪种分页方法取决于你的具体需求:

• 如果你只需要简单的分页逻辑且结果数量较少,可以使用`from/size`。

• 如果你需要处理大量数据且不需要实时性,可以使用`scroll`API。

• 如果你需要按排序顺序分页且实时性要求高,可以使用`search_after`。

• 如果你需要在分页过程中保持数据一致性,可以结合使用 PIT 和`search_after`。

希望这些内容能帮助你更好地理解和选择适合的分页方法!  

标签:

【Elasticsearch】分页查询由讯客互联人工智能栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“【Elasticsearch】分页查询