ElasticSearch 分页查询

Elasticsearch分页查询 在默认情况下, Elasticsearch 查询返回前10条匹配的文档。 为了对大批量查询结果分页,最简单方式是在查询A

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