Elasticsearch分页查询
在默认情况下, Elasticsearch 查询返回前10条匹配的文档。
为了对大批量查询结果分页,最简单方式是在查询API中添加from和size参数,from表示需要返回的满足查询条件的数量,size表示查询起始数据在全量结果集中的偏移量。
创建实验索引:
PUT linked_blog
{
"mappings": {
"default": {
"properties": {
"title": {
"type": "text",
"analyzer" : "ik_max_word",
"search_analyzer" : "ik_smart",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
GET /linked_blog/default/_search
{
"query": {
"match_all": {}
},
"from": 3,
"size": 2
}
默认情况下,使用from+size参数不能分页查询超过10,000
的文档。该限制可以通过索引设置index.max_result_window
更改。
深度分页可能造成慢查询。查询结果在返回之前会被排序。但查询通常会跨越多个分片,每个分片先自各执行查询,并各自排序,再将各个分片查询结果合并以保证最终查询结果顺序正确。
为了避免该问题,Elasticsearch官方推荐使用 scroll
或者 search_after
替代from+size
实现分页。
Elasticsearch的查询是分2个阶段进行的,即Query阶段和Fetch阶段。 Query阶段比较轻量级,通过查询倒排索引,获取满足查询结果的文档ID列表。 而Fetch阶段比较重,需要将每个shard的结果取回。
Scroll查询,先做轻量级的Query阶段以后,免去了繁重的全局排序过程。 它只是将查询结果集,也就是文档ID列表保留在一个上下文里, 之后分批Fetch。
Scroll查询方式类似于传统数据库的游标查询和Redis的scan查询。如果使用过Twitter开发者API会发现,这种方式和使用Twitter开发者API获取博文也很类似。
为了使用scroll,只需在首次分页查询时加上scroll参数,其值为该查询上下文存活时间。例如:
GET /linked_blog/default/_search?scroll=1m
{
"query": {
"match_all": {}
},
"size": 3
}
上面的查询结果中回包含_scroll_id
字段。
{
"_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAARo3hFkVENTdJaTEtUXJhX21hREhweVRaeHcAAAAAACs9lRY4aFpJa0dTRlQ0T2dLWVNxRFNQWFBBAAAAAAAx4D8WQXhkeGc4Ri1UTlc2a1N3OFAtTWVVUQAAAAAARo3iFkVENTdJaTEtUXJhX21hREhweVRaeHcAAAAAAAA9FxZzNnZWQ3duSlF2aTE5bHBUd2ZyN0RB",
"took": 4580,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 12,
"max_score": 1,
"hits": [
{
"_index": "linked_blog",
"_type": "default",
"_id": "AXTPePEMqi9OmRTyD92V",
"_score": 1,
"_source": {
"title": "MyBatis如何调用mysql数据库存储过程"
}
},
{
"_index": "linked_blog",
"_type": "default",
"_id": "AXTPdUHfqi9OmRTyD92C",
"_score": 1,
"_source": {
"title": "oracle数据库创建表空间、创建用户、用户赋权限"
}
},
{
"_index": "linked_blog",
"_type": "default",
"_id": "AXTPe6oYqi9OmRTyD92m",
"_score": 1,
"_source": {
"title": "Nginx+Tomcat——负载均衡与动静分离群集 理论知识+实验部署+报错排坑 详细讲解一看秒懂!!!"
}
}
]
}
}
后面的分页使用scroll查询,并带上上次查询结果的_scroll_id
字段。直到hits
数组空,即表示所有数据查询完成。
POST /_search/scroll
{
"scroll" : "1m",
"scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAARo3hFkVENTdJaTEtUXJhX21hREhweVRaeHcAAAAAACs9lRY4aFpJa0dTRlQ0T2dLWVNxRFNQWFBBAAAAAAAx4D8WQXhkeGc4Ri1UTlc2a1N3OFAtTWVVUQAAAAAARo3iFkVENTdJaTEtUXJhX21hREhweVRaeHcAAAAAAAA9FxZzNnZWQ3duSlF2aTE5bHBUd2ZyN0RB"
}
结果如下
{
"_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAARo3hFkVENTdJaTEtUXJhX21hREhweVRaeHcAAAAAACs9lRY4aFpJa0dTRlQ0T2dLWVNxRFNQWFBBAAAAAAAx4D8WQXhkeGc4Ri1UTlc2a1N3OFAtTWVVUQAAAAAARo3iFkVENTdJaTEtUXJhX21hREhweVRaeHcAAAAAAAA9FxZzNnZWQ3duSlF2aTE5bHBUd2ZyN0RB",
"took": 1911,
"timed_out": false,
"terminated_early": true,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 12,
"max_score": 1,
"hits": [
{
"_index": "linked_blog",
"_type": "default",
"_id": "AXTPfEmrqi9OmRTyD92p",
"_score": 1,
"_source": {
"title": "Nginx+Tomcat高可用负载均衡群集——动静分离"
}
},
{
"_index": "linked_blog",
"_type": "default",
"_id": "AXTPc15Qqi9OmRTyD911",
"_score": 1,
"_source": {
"title": "关于MeterSphere的性能测试架构理解"
}
},
{
"_index": "linked_blog",
"_type": "default",
"_id": "AXTPd6q0qi9OmRTyD92Q",
"_score": 1,
"_source": {
"title": "利用HikariCP大幅提升性能"
}
}
]
}
}
初次分页和后面每次分页均返回_scroll_id。 尽管_scroll_id在两次请求之间可能会发生变化,但并非总是会发生变化。在任何情况下,都应使用最近收到的_scroll_id。
scroll 在首次分页时就已经将所有满足查询条件的文档确定了,并忽略后续对这些文档的修改(可以理解,Elasticsearch使用版本号区分修改)。并在首次分页时,查询上下文也已经创建了,在后续分页中可以通过scroll_id 区分该查询上下文,并使其保持存活状态。
scroll 参数的值告诉Elasticsearch 查询上下文存活时间。该值并非处理所有分页数据所需时间,而是处理当前页所需时间,每次分页都会重新设置查询上下文过期时间。如果一个scroll 请求没有设置scroll 参数,则表示该查询上下文将会被释放。
使用如下接口,可以查询每个节点打开了多少scroll 查询上下文。
GET /_nodes/stats/indices/search
scroll 查询上下文在存活时间到达后会被自动删除。因为scroll 查询上下文需要消耗资源,所以当分页结束或者不再需要分页时,需要及时手动关闭。手动关闭API如下:
DELETE /_search/scroll
{
"scroll_id" : "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ=="
}
关闭多个scroll 查询上下文可用如下API:
DELETE /_search/scroll
{
"scroll_id" : [
"DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAAD4WYm9laVYtZndUQlNsdDcwakFMNjU1QQ==",
"DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAABFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAAAxZrUllkUVlCa1NqNmRMaUhiQlZkMWFBAAAAAAAAAAIWa1JZZFFZQmtTajZkTGlIYkJWZDFhQQAAAAAAAAAFFmtSWWRRWUJrU2o2ZExpSGJCVmQxYUEAAAAAAAAABBZrUllkUVlCa1NqNmRMaUhiQlZkMWFB"
]
}
如下API可以清除所有scroll 查询上下文:
DELETE /_search/scroll/_all