【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`。
希望这些内容能帮助你更好地理解和选择适合的分页方法!