【Elasticsearch入门到落地】15、DSL排序、分页及高亮
接上篇《14、DSL复合查询》
上一篇我们讲解了DSL的复合查询,学习“相关性算分”、“FunctionScoreQuery”和“BooleanQuery”的相关内容。本篇我们来学习ElasticSearch的DSL搜索结果处理(排序、分页及高亮)。
一、引言
Elasticsearch作为一款强大的搜索引擎,其DSL(Domain Specific Language)提供了丰富的查询和结果处理能力。在实际应用中,我们经常需要对搜索结果进行排序、分页和高亮显示,以提升用户体验。本文将详细介绍如何使用Elasticsearch DSL对搜索结果进行处理,并结合酒店搜索的实际案例进行演示。
二、DSL搜索结果排序
1. 排序基础
Elasticsearch默认按照相关性得分(_score)降序排列结果。相关性得分是根据查询条件与文档匹配程度计算得出的(类似加了"sort":["_score"])。
{"query": {"match": {"name": "上海酒店"}}
}
查询结果:
对于不需要计算相关性的场景,可以使用_doc按索引顺序排序:
{"query": {"match": {"name": "上海酒店"}},"sort": ["_doc"]
}
查询结果:
2. 单字段与多字段排序
我们可以按照酒店价格升序排列:
{"query": {"match": {"city": "上海"}},"sort": [{"price": {"order": "asc"}}]
}
查询结果:
也可以按照评分降序、价格升序的多字段排序(按优先级逐级排序):
{"query": {"match": {"city": "上海"}},"sort": [{"score": {"order": "desc"}},{"price": {"order": "asc"}}]
}
查询结果:
这里大家可能会注意到,价格好像未严格按升序排列,第一个“上海虹桥金臣皇冠假日酒店”的价格2488元要比下面“上海南青华美达酒店”的299元高得多,尽管评分确实是升序的,这里需要解释一下:
(1)多字段排序的优先级规则
咱们的排序条件为score降序优先,price升序次之。这意味着:
第一优先级:所有文档先按score从高到低排序(48 > 47 > 46)
第二优先级:仅在score相同的文档组内,price才会按升序排列
从结果可见:
score=48的文档只有1条(价格2488),无需比较价格
score=47的文档组中,价格确实按升序排列:299 < 529 < 809 < 889 < 891 < 922 < 924
score=46的文档组中,价格也是升序:408 < 592
(2)跨分数段的价格比较无意义
不同score分段的文档之间不会比较价格。例如:
score=47组的最低价格是299,但score=46组的408比它高,这是允许的,因为排序逻辑是先分组再组内排序,而非全局统一排序。
3.地理坐标排序(_geo_distance)
我们也可以按照地理位置距离进行排序,如按酒店距离用户当前位置(如上海中心大厦:31.2334°N,121.5050°E)由近到远排序,并限制距离在10公里内。DSL查询(假设hotel索引的location字段为geo_point类型):
{"query": {"bool": {"filter": {"geo_distance": {"distance": "10km","location": { "lat": 31.2334,"lon": 121.5050}}}}},"sort": [{"_geo_distance": {"location": { // 目标坐标"lat": 31.2334,"lon": 121.5050},"order": "asc", // 升序(由近到远)"unit": "km", // 距离单位"mode": "min" // 若location为多值,取最近距离排序}}]
}
响应关键字段:
hits.hits.sort:包含文档到目标点的实际距离(单位:km)。
若距离超出10km的文档会被过滤掉(因geo_distance过滤条件)。
查询结果:
4. 高级排序:脚本排序(script)
对于更复杂的排序需求,可以使用脚本排序。例如,按照价格与评分的乘积排序:
{"query": {"match": {"city": "上海"}},"sort": {"_script": {"type": "number","script": {"lang": "painless","source": "doc['price'].value * doc['score'].value"},"order": "desc"}}
}
5. 应用场景
在酒店搜索中,常见的排序场景包括:
按价格排序(升序或降序)
按评分排序(高评分优先)
按距离排序(结合地理位置)
综合排序(如评分+价格的加权排序)
6.注意事项
(1)字段类型限制
排序字段必须为keyword、数值(numeric)或日期(date)类型,避免直接对text类型字段排序(因其分词后无法直接排序)若需对文本内容排序,应使用.keyword子字段。
示例错误场景:
"sort": [{ "name": "asc" }] // 错误:name是text类型
修正为:
"sort": [{ "name.keyword": "asc" }] // 使用keyword子字段
(2)性能优化
Elasticsearch默认对非文本字段启用doc_values(列式存储结构),加速排序和聚合操作。但需注意:避免对高基数(唯一值多)字段排序,可能增加内存和磁盘压力。可通过"docvalue_fields": ["price"]显式指定从列存读取字段值,减少序列化开销。
三、DSL搜索结果分页
1. 基础分页
Elasticsearch使用from和size参数实现分页:
{"query": {"match": {"city": "上海"}},"from": 0,"size": 10,"sort": [{"price": {"order": "asc"}}]
}
from: 起始位置(从0开始)
size: 每页大小
2. 深度分页问题与解决方案
当from值很大时(如超过10000),会出现性能问题。这是因为Elasticsearch需要将所有匹配的文档排序后才能确定哪些文档应该返回。
解决方案:
(1)使用search_after:
{"query": {"match": {"city": "上海"}},"size": 10,"sort": [{"price": {"order": "asc"}}],"search_after": [336]
}
查询结果:
search_after参数指定上一页最后一条记录的排序字段值。
(2)使用Scroll API:
对于大量数据导出场景,可以使用Scroll API:
初始化 Scroll 请求
GET /hotel/_search?scroll=5m
{"query": {"match": {"city": "上海"}},"size": 100,"sort": ["_doc"]
}
scroll=5m意思是初始Scroll请求设置的有效期5分钟。
查询结果:
然后通过scroll_id获取后续批次:
POST /_search/scroll
{"scroll": "5m", // 每次请求需刷新超时时间"scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAABCzA..."
}
查询结果:
3. 实践建议
避免深度分页,尽量限制用户翻页深度
对于后台导出等场景,使用Scroll API
前端实现"加载更多"功能时,使用search_after
四、DSL搜索结果高亮
1. 高亮原理
高亮功能可以将匹配的搜索词用HTML标签包裹,便于前端展示。
2. 实现方法
基本高亮查询:
{"query": {"match": {"name": "万豪"}},"highlight": {"fields": {"name": {}}}
}
查询结果:
HTML显示效果:
自定义高亮标签:
{"query": {"match": {"name": "万豪"}},"highlight": {"pre_tags": ["<strong>"],"post_tags": ["</strong>"],"fields": {"name": {}}}
}
查询结果:
HTML显示效果:
3. 优化技巧
确保高亮字段与搜索字段一致
对于大文本字段,可以使用fragment_size控制高亮片段长度
可以使用number_of_fragments限制返回的高亮片段数量
五、综合案例演示
1. 酒店搜索场景
假设我们需要实现以下搜索需求:
●搜索上海的酒店
●按价格升序排列
●分页显示,每页10条
●高亮显示酒店名称中的搜索词
2. 代码示例
{"query": {"bool": {"must": [{"match": {"city": "上海"}}],"filter": [{"range": {"price": {"gte": 300,"lte": 1000}}}]}},"from": 0,"size": 10,"sort": [{"price": {"order": "asc"}}],"highlight": {"fields": {"name": {}}}
}
注:Elasticsearch高亮默认仅对搜索字段(即query中指定的city字段)生效。虽然我们在highlight.fields中指定了name,但未在查询条件中匹配该字段,因此不会触发高亮。所以这里我们添加require_field_match: false参数,允许高亮非搜索字段。
查询结果:
以上为DSL查询结果的排序、分页及高亮内容的总结。下一篇我们来学习使用Java编程语言操作RestClient进行文档的查询。
转载请注明出处:https://blog.csdn.net/acmman/article/details/149156295