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排序与分页:

  1. 支持多字段排序和脚本排序
  2. from/size适用于浅分页
  3. scroll适用于数据导出
  4. search_after适用于深度分页
  5. PIT提供一致性视图
  6. 合理选择分页方案

下一步,我们将学习搜索建议。

最后更新:2026-03-27