分页查询执行原理
POST /my_index/my_type/_search
{
"query": { "match_all": {}},
"from": 100,
"size": 10
}
- Client 发送一次搜索请求,node1 接收到请求,然后,node1 创建一个大小为from + size的优先级队列用来存结果,我们管 node1 叫 coordinating node。
- coordinating node将请求广播到涉及到的 shards,每个 shard 在内部执行搜索请求,然后,将结果存到内部的大小同样为from + size 的优先级队列里,可以把优先级队列理解为一个包含top N结果的列表。
- 每个 shard 把暂存在自身优先级队列里的数据返回给 coordinating node,coordinating node 拿到各个 shards 返回的结果后对结果进行一次合并,产生一个全局的优先级队列,存到自身的优先级队列里。
Fetch阶段
- 取出对应区间的_id,coordinating node 发送 GET 请求到相关shards
- shard 根据 doc 的_id取到数据详情,然后返回给 coordinating node
- coordinating node 返回数据给 Client。
存在问题
- 随着分页深度的增加,查询成本越来越高,性能越来越差
- 这种请求并不合理,因为通常只需要前几条数据,很多查询的数据是无用的
size 不能超过index.max_result_window这个参数的值,默认10000
深度分页解决方案
sroll
场景
无法实时搜索,所以不适合实时场景
-
原理
类似关系型数据库的cursor,相当于维护了一份当前索引数据的快照,所有查询数据来源于这份快照
sroll可以分为两部分
初始化
// 参数 scroll,表示暂存搜索结果的时间
POST /twitter/tweet/_search?scroll=1m
{
"size": 100,
"query": {
"match" : {
"title" : "elasticsearch"
}
}
}
// 会返回一个_scroll_id,_scroll_id用来下次取数据用。
遍历
POST /_search?scroll=1m
{
"scroll_id":"XXXXXXXXXXXXXXXXXXXXXXX I am scroll id XXXXXXXXXXXXXXX"
}
优缺点
search_type:赋值为scan,表示采用 Scroll Scan 的方式遍历,同时告诉 Elasticsearch 搜索结果不需要排序。
- scroll:同上,传时间。
- size:与普通的 size 不同,这个 size 表示的是每个 shard 返回的 size 数,最终结果最大为 number_of_shards * size。
「Scroll Scan与Scroll的区别」
- Scroll-Scan结果「没有排序」,按index顺序返回,没有排序,可以提高取数据性能。
- 初始化时只返回 _scroll_id,没有具体的hits结果
size控制的是每个分片的返回的数据量,而不是整个请求返回的数据量。
search after
基本使用
POST twitter/_search
{
"size": 10,
"query": {
"match" : {
"title" : "es"
}
},
"sort": [
{"date": "asc"},
{"_id": "desc"}
]
}
返回结果
{
"took" : 29,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 5,
"relation" : "eq"
},
"max_score" : null,
"hits" : [
{
...
},
"sort" : [
...
]
},
{
...
},
"sort" : [
124648691,
"624812"
]
}
]
}
}
上面的请求会为每一个文档返回一个包含sort排序值的数组。
- 这些sort排序值可以被用于search_after参数里以便抓取下一页的数据。
比如,我们可以使用最后的一个文档的sort排序值,将它传递给search_after参数:
GET twitter/_search
{
"size": 10,
"query": {
"match" : {
"title" : "es"
}
},
"search_after": [124648691, "624812"],
"sort": [
{"date": "asc"},
{"_id": "desc"}
]
}
若我们想接着上次读取的结果进行读取下一页数据,第二次查询在第一次查询时的语句基础上添加search_after,并指明从哪个数据后开始读取。
基本原理
es维护一个实时游标,它以上一次查询的最后一条记录为游标,方便对下一页的查询,它是一个无状态的查询,因此每次查询的都是最新的数据。
- 由于它采用记录作为游标,因此「SearchAfter要求doc中至少有一条全局唯一变量(每个文档具有一个唯一值的字段应该用作排序规范)」
优点