Search_after使用
前言
这是我在这个网站整理的笔记,有错误的地方请指出,关注我,接下来还会持续更新。
作者:神的孩子都在歌唱
一. 简介
search_after
是 Elasticsearch 提供的一种分页查询方式,它可以用来在已经排序的结果集中进行分页查询。
search_after查询步骤如下(下面有具体的例子帮助理解):
最后一条排序结果相当于它的游标
优点:
- 性能优势: 相对于传统的
from
和size
参数来说,search_after
在处理大量数据时性能更好,因为它不需要跳过之前的结果集,不严格受制于 max_result_window,可以无限制往后翻页。from
和size
只能翻页10000条. - 适用于实时数据: 在实时数据更新频繁的场景下,
search_after
可以确保查询结果的准确性,因为它不会受到新数据插入的影响。 - 避免深度分页问题: 使用
search_after
可以避免深度分页问题,即当页数很大时,传统的分页方式性能会下降。
缺点:
- 需要结果排序: 使用
search_after
前需要对结果集进行排序,如果排序字段较多或者数据量较大,可能会影响性能。 - 只适用于唯一排序字段:
search_after
只支持基于唯一排序字段的分页查询,如果有多个排序字段,需要确保排序字段的唯一性。 - 不支持随机访问: 由于
search_after
是基于上一页的最后一个文档进行分页,所以不支持随机访问,只能逐页查询。
使用场景:
- 大数据量分页查询: 当需要处理大量数据并进行分页查询时,
search_after
可以提供更好的性能。 - 实时数据展示: 在实时数据展示的场景下,可以使用
search_after
来确保查询结果的准确性。 - 避免深度分页问题: 当需要避免深度分页问题时,可以考虑使用
search_after
来提高查询效率。
官方文档说明不再建议使用scroll滚动分页和from size分页,建议使用search_after
We no longer recommend using the scroll API for deep pagination. If you need to preserve the index state while paging through more than 10,000 hits, use the search_after parameter with a point in time (PIT).
我们不再建议使用滚动 API 进行深度分页。如果需要在分页超过 10,000 个命中时保留索引状态,请使用带有时间点 (PIT) 的 search_after 参数。
By default, you cannot use
from
andsize
to page through more than 10,000 hits. This limit is a safeguard set by theindex.max_result_window
index setting. If you need to page through more than 10,000 hits, use thesearch_after
parameter instead.默认情况下,您不能使用
from
和size
翻阅超过 10,000 个点击。该限制是由索引设置设置的保障措施index.max_result_window
。如果您需要翻阅超过 10,000 个点击,请使用search_after
参数代替。
二. 不带PIT的search_after查询
建议带PIT,我举的这个列子是帮助理解PIT的作用
2.1 构造数据
PUT /test/_bulk?refresh {"index":{}} {"name": "小狗", "leg": 4, "iswing": false} {"index":{}} {"name": "小鸡", "leg": 2, "iswing": true} {"index":{}} {"name": "小猫", "leg": 4, "iswing": false}
2.2 search_after分页查询
注意:当我们使用search_after时,from值必须设置为0或者-1。
首先我们通过排序查询10条数据
GET /test/_search { "size": 10, "sort": [ { "name.keyword": { "order": "desc" // 对返回的值进行排序 } } ] }
要获取下一页结果,需要使用最后一条文档的排序值(也就是sort列表里面的值) 作为 search_after 参数重新运行上一个搜索。
GET test/_search { "size": 10, "search_after": ["小鸡"], "sort": [ { "name.keyword": { "order": "desc" } } ] }
这样子他就会从排序好的name为小猫开始查询
2.2 问题
每次检索新的结果页时更新数组,重复此过程。如果这些请求之间发生刷新,结果的顺序可能会发生变化,从而导致页面之间的结果不一致。为了防止这种情况,您可以创建一个时间点 (PIT) 来在搜索中保留当前索引状态。
排序的值不唯一,翻页的时候文档对应不上。为了防止这种情况,PIT 搜索请求都会添加一个名为 _shard_doc 的隐式排序字段,该字段也可以显式提供,这个字段在es中叫做 tiebreaker 。此字段包含每个文档的唯一值。如果您不包含tiebreaker字段,则分页结果可能会丢失或重复命中。
比如我在插入一只小鸟
PUT /test/_bulk?refresh {"index":{}} {"name": "小鸟", "leg": 2, "iswing": true}
在执行查询语句
GET test/_search { "size": 10, "search_after": ["小鸡"], "sort": [ { "name.keyword": { "order": "desc" } } ] }
可以看到查询的结果发生了变化,并且第11条应该还是小鸡而不是小鸟
为了解决这种情况,es在7.x引入了PIT的概念,它相当于是 存储索引数据状态的轻量级视图。
三. 带PIT的search_after查询
一些关于PIT的知识:
PIT的快照时间点: 创建 PIT 时记录的是索引状态的快照,而不是实时数据。即使 PIT 不过期,它也只反映创建 PIT 时的索引状态,而不包括之后新增的数据。
数据更新延迟: 即使使用 PIT 进行查询,由于数据写入和索引过程中可能存在一定的延迟,新数据可能不会立即反映在查询结果中。这种延迟可能导致查询结果不是实时的。
实时性需求: 如果需要实时性较高的查询结果,可能需要结合其他机制或策略来确保数据的实时性,如定时刷新 PIT、定时重新创建 PIT 等。
PIT对于翻页的作用:PIT确保了在后续翻页的过程中,可能会有新数据写入等操作,但这些操作不会对原有结果集构成影响,保障数据的一致性。
关于 pit的官方文档
3.1 构建第一次查询条件
POST /test/_pit?keep_alive=1m
keep_alive必须要加上,它表示这个pit能存在多久,这里设置的是1分钟
构建第一次查询条件
GET /_search { "size": 10, "pit": { "id": "z9_qAwELdGVzdC0wMDAwMDQWVGxjUUVIUzhRQktTTkJRU3VQQXlodwAWWGlMYTRUQ2VUaE9PVlJHNzRTdHBVdwAAAAAAAAauuRZ3bEkwVkx1MlR6YVlsMUZ4MHpUV05nAAEWVGxjUUVIUzhRQktTTkJRU3VQQXlodwAA", "keep_alive":"1m" }, "sort": [ { "name.keyword": { "order": "desc" } } ] }
输出值如下
我上面展示的是最后一条文档,可以看到排序sort中莫名多了个28 ,这在es官方文档中叫做tiebreaker ,官方文档中解释如下
如果您使用 PIT,tiebreaker 是 隐含的排序值,是基于_shard_doc 的升序排序方式。 _shard_doc 值是 PIT 中的分片索引和 Lucene 的内部文档 ID 的组合,它对于每个文档都是唯一的 。您还可以在搜索请求中手动添加 tiebreaker 以自定义顺序:
网上解释:tiebreaker (决胜字段),tiebreaker 等价于_shard_doc。tiebreaker 本质是每个文档的唯一值,确保分页不会丢失或者分页结果数据出现重复(相同页重复或跨页重复)。
可以在sort里面加上_shard_doc 进行自定义排序
"sort": [ { "name.keyword": { "order": "desc" }, "_shard_doc": "asc" }
3.2 进行下一页查询
我们在拿它sort里面的值放入到search_after中进行下一页查询
在每个搜索请求中添加 keep_alive 参数来延长 PIT 的保留期,相当于是重置了一下时间
GET /_search { "size": 10, "pit": { "id": "z9_qAwELdGVzdC0wMDAwMDQWVGxjUUVIUzhRQktTTkJRU3VQQXlodwAWWGlMYTRUQ2VUaE9PVlJHNzRTdHBVdwAAAAAAAAauuRZ3bEkwVkx1MlR6YVlsMUZ4MHpUV05nAAEWVGxjUUVIUzhRQktTTkJRU3VQQXlodwAA", "keep_alive":"1m" }, "sort": [ { "name.keyword": { "order": "desc" } } ], "search_after": [ "小鸡", 28 ], "track_total_hits": false // 禁用总点击率跟踪以加快分页速度 }
通过以下输出可以看到。我们已经成功进入到下一页了
这样子就能够成功进行分页查询了
3.3 删除PIT
完成后,您应该删除您的 PIT。
DELETE /_pit { "id" : "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==" }
四.参考文章
https://blog.csdn.net/qq_26857259/article/details/134372438
https://blog.csdn.net/yangbindxj/article/details/123979413 有上一页方案
作者:神的孩子都在歌唱
本人博客:https://blog.csdn.net/weixin_46654114
转载说明:务必注明来源,附带本人博客连接。