Elasticsearch排序与分页 #
一、排序 #
1.1 基本排序 #
bash
GET /products/_search
{
"query": {
"match_all": {}
},
"sort": [
{ "price": "desc" }
]
}
1.2 多字段排序 #
bash
GET /products/_search
{
"query": {
"match_all": {}
},
"sort": [
{ "price": "desc" },
{ "created_at": "asc" },
"_score"
]
}
1.3 排序选项 #
bash
GET /products/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"price": {
"order": "desc",
"mode": "avg",
"missing": "_last",
"nested": {
"path": "variants",
"filter": {
"term": { "variants.available": true }
}
}
}
}
]
}
| 参数 | 说明 |
|---|---|
| order | 排序方向(asc/desc) |
| mode | 多值字段的排序方式 |
| missing | 缺失值处理 |
| nested | 嵌套对象排序 |
1.4 mode选项 #
| mode | 说明 |
|---|---|
| min | 最小值 |
| max | 最大值 |
| sum | 总和 |
| avg | 平均值 |
| median | 中位数 |
1.5 文本字段排序 #
bash
GET /products/_search
{
"query": {
"match_all": {}
},
"sort": [
{ "name.keyword": "asc" }
]
}
1.6 基于得分排序 #
bash
GET /products/_search
{
"query": {
"match": { "name": "iPhone" }
},
"sort": [
"_score"
]
}
1.7 脚本排序 #
bash
GET /products/_search
{
"query": {
"match_all": {}
},
"sort": {
"_script": {
"type": "number",
"script": {
"source": "doc['price'].value * params.factor",
"params": {
"factor": 1.2
}
},
"order": "desc"
}
}
}
1.8 地理距离排序 #
bash
GET /stores/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"_geo_distance": {
"location": {
"lat": 40.7128,
"lon": -74.0060
},
"order": "asc",
"unit": "km",
"distance_type": "arc"
}
}
]
}
二、分页 #
2.1 基本分页 #
bash
GET /products/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 10
}
| 参数 | 说明 | 默认值 |
|---|---|---|
| from | 起始位置 | 0 |
| size | 返回数量 | 10 |
2.2 分页限制 #
默认 from + size 不能超过 10000:
bash
GET /products/_search
{
"query": { "match_all": {} },
"from": 10000,
"size": 10
}
错误:
json
{
"error": {
"type": "illegal_argument_exception",
"reason": "Result window is too large, from + size must be less than or equal to: [10000]"
}
}
2.3 修改限制 #
bash
PUT /products/_settings
{
"index": {
"max_result_window": 50000
}
}
注意:不推荐修改,会影响性能。
三、深度分页解决方案 #
3.1 Scroll API #
创建scroll:
bash
POST /products/_search?scroll=1m
{
"size": 100,
"query": {
"match_all": {}
}
}
响应:
json
{
"_scroll_id": "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFl...",
"hits": {
"hits": [...]
}
}
继续获取:
bash
POST /_search/scroll
{
"scroll": "1m",
"scroll_id": "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFl..."
}
清除scroll:
bash
DELETE /_search/scroll
{
"scroll_id": "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFl..."
}
清除所有:
bash
DELETE /_search/scroll/_all
3.2 search_after #
首次查询:
bash
GET /products/_search
{
"size": 10,
"query": {
"match_all": {}
},
"sort": [
{ "created_at": "asc" },
{ "_id": "asc" }
]
}
响应:
json
{
"hits": {
"hits": [
{
"_id": "1",
"sort": ["2024-01-01", "1"]
}
]
}
}
下一页:
bash
GET /products/_search
{
"size": 10,
"query": {
"match_all": {}
},
"sort": [
{ "created_at": "asc" },
{ "_id": "asc" }
],
"search_after": ["2024-01-01", "1"]
}
3.3 方案对比 #
| 方案 | 适用场景 | 特点 |
|---|---|---|
| from/size | 浅分页 | 简单,有限制 |
| scroll | 数据导出 | 占用资源,不实时 |
| search_after | 深度分页 | 实时,需要排序 |
3.4 PIT (Point In Time) #
创建PIT:
bash
POST /products/_pit?keep_alive=1m
响应:
json
{
"id": "46ToAwMDaWR5BXV1aWQyFg..."
}
使用PIT查询:
bash
GET /_search
{
"size": 10,
"query": {
"match_all": {}
},
"pit": {
"id": "46ToAwMDaWR5BXV1aWQyFg...",
"keep_alive": "1m"
},
"sort": [
{ "_shard_doc": "asc" }
]
}
删除PIT:
bash
DELETE /_pit
{
"id": "46ToAwMDaWR5BXV1aWQyFg..."
}
四、分页最佳实践 #
4.1 场景选择 #
text
分页方案选择
├── 浅分页(< 100页)
│ └── from/size
├── 数据导出
│ └── scroll
├── 深度分页
│ └── search_after
└── 一致性视图
└── PIT + search_after
4.2 性能优化 #
text
性能优化建议
├── 控制size大小
│ └── 建议10-100
├── 使用filter
│ └── 减少计算量
├── 只返回必要字段
│ └── 使用_source过滤
└── 合理设置排序
└── 使用索引字段
4.3 用户体验 #
text
用户体验优化
├── 游标分页
│ └── 使用search_after
├── 无限滚动
│ └── 前端实现
└── 总数显示
│ └── 使用track_total_hits
五、track_total_hits #
5.1 精确计数 #
bash
GET /products/_search
{
"query": {
"match_all": {}
},
"track_total_hits": true
}
5.2 限制计数 #
bash
GET /products/_search
{
"query": {
"match_all": {}
},
"track_total_hits": 10000
}
超过10000不再精确计数。
5.3 禁用计数 #
bash
GET /products/_search
{
"query": {
"match_all": {}
},
"track_total_hits": false
}
六、排序与分页组合 #
6.1 完整示例 #
bash
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "name": "iPhone" } }
],
"filter": [
{ "term": { "brand": "Apple" } },
{ "range": { "price": { "lte": 1500 } } }
]
}
},
"from": 0,
"size": 20,
"sort": [
{ "_score": "desc" },
{ "price": "asc" },
{ "created_at": "desc" }
],
"_source": ["name", "price", "brand"],
"track_total_hits": 1000
}
6.2 响应结构 #
json
{
"took": 5,
"hits": {
"total": {
"value": 100,
"relation": "eq"
},
"max_score": null,
"hits": [
{
"_id": "1",
"_score": null,
"sort": [1.5, 999, 1704067200000],
"_source": {
"name": "iPhone 15",
"price": 999,
"brand": "Apple"
}
}
]
}
}
七、总结 #
本章介绍了Elasticsearch排序与分页:
- 支持多字段排序和脚本排序
- from/size适用于浅分页
- scroll适用于数据导出
- search_after适用于深度分页
- PIT提供一致性视图
- 合理选择分页方案
下一步,我们将学习搜索建议。
最后更新:2026-03-27