elasticsearch中的排序与相关性
排序与相关性简介
- 默认情况下,返回的结果是按照相关性进行排序的,最相关的文档在最前面,
后面会解析相关性意味着什么以及如何计算,先来看看sort参数以及如何使用它
排序
- 为了按照相关性进行排序,需要将相关性设置为一个数值,es中相关性是由一个浮点数表示,
并在搜索结果树中通过_score返回,默认排序是_score降序 - 有的时候相关性评分并没有意义
GET /us/_search
{
"query": {
"bool": {
"filter": {
"term": {
"date": "2014-09-20"
}
}
}
}
}
因为我们使用的是filter过滤,所以没有进行评分,_score默认值为0.0,
如果评分为0.0对你造成困扰,可以使用constant_score
{
"query": {
"constant_score": {
"filter": {
"term": {
"date": "2014-09-20"
}
}
}
}
}
这将让所有文档应用一个恒定分数(默认为1),它将执行与bool查询相同,性能相同,并且所有文档
像bool一样随机返回,这些文档只是有了一个分数,而不是0
- 按照字段的值进行排序
在这个案例中,通过时间对tweet排序是有意义的,最新的tweet在前面,通过sort参数可以实现
GET /us/_search
{
"query": {
"match_all": {}
},
"sort": {
"date": {
"order": "desc"
}
}
}
结果:
_score不被计算,因为它并没有用于排序
date字段的值表示未毫秒数,通过sort字段返回
_score和max_score字段都是null,计算_score花销巨大,通常仅用于排序,我们并不根据相关性进行排序
所以记录_score是没有意义的,如果你无论如何都要计算_score,可以设置这个参数:track_scores: true
一个简单的方法按照字段升序排序
{
"query": {
"match_all": {}
},
"sort": "date", // 按照date字段升序排序
"track_scores": true // 始终计算_score
}
- 多级排序
如果我们想要按照date和_score进行排序,并且匹配的结果首先按照日期排序,然后按照相关性进行排序
GET /us/_search
{
"query": {
"match": {
"name": "john"
}
},
"sort": [
{
"date": {
"order": "desc"
}
},
{
"_score": {
"order": "desc"
}
}
]
}
先按照第一个条件排序,当第一个条件相同时才按照第二个条件排序
多级排序并不一定包含_score,也可以根据不同的字段进行排序,
- query-string搜索也支持自定义排序,可以在查询字符串中使用sort参数
GET/us/_search?q=tweet:is&sort=_score:desc
- 多值字段的排序
一种情形是字段有多个值的排序, 需要记住这些值并没有固有的顺序;一个多值的字段仅仅是多个值的包装,这时应该选择哪个进行排序呢?
对于数字或日期,你可以将多值字段减为单值,这可以通过使用 min 、 max 、 avg 或是 sum 排序模式 。
例如你可以按照每个 date 字段中的最早日期进行排序,通过以下方法:
"sort": {
"dates": {
"order": "asc",
"mode": "min"
}
}
字符串排序与多字段
我们可以按照tweet全文域字段进行搜索,使用tweet.keyword精确值域字段进行排序进行降序排序
GET /us/_search
{
"query": {
"match": {
"tweet": "is"
}
},
"sort": {
"tweet.keyword": {
"order": "desc"
}
}
}
- 以全文analyzed字段进行排序,会消耗大量的内存
什么是相关性
- 默认情况下,返回的搜索结果是按照相关性倒叙排序的,但是什么是相关性?如何计算相关性?
每一个文档都有一个相关性评分,用一个正浮点数_score表示,_score的评分越高,相关性越高
查询语句会为每一个文档生成一个_score字段,
- es的相似度算法被定义为检索词频率/反向文档频率,TF/IDF
- 检索词频率TF(term frequency)
检索词在该字段出现的频率,出现频率越高,相关性也越高,字段中出现过5次要出现过1次的相关性高 - 反向文档频率IDF(document frequency)
每个检索词在索引中的出现频率,频率越高,相关性月底,检索词出现在多数文档中会比出现在少数文档中权重更低 - 字段长度准则
字段的长度是多少?长度越长,相关性越低,检索词在一个短的title要比一个长的content权重更大
单个查询可以联合使用TF/IDF和其它方式 - 相关性并不只是全文本检索的专利,也适用于yes|no子句,匹配的子句越多,相关性评分越高
如果多条子句被合并为一条复合的查询语句,例如bool查询,则每个查询子句计算出的评分会被合并到总的相关评分当中去
- 理解评分标准
当调试一条比较复杂的查询语句时,想理解如何计算是比较困难的,es在每个查询语句中都有一个explain参数,
将explain参数设置为true就可以获取更多详细的信息
GET /us/_search?explain=true
{
"query": {
"match_phrase": {
"tweet": "it"
}
}
}
explain参数可以让返回结果添加一个_score评分结果得来依据
"_shard" : 1,
"_node" : "mzIVYCsqSWCG_M_ZffSs9Q",
注意返回值加入了该文档来自于哪个节点哪个分片上的信息,因为检索词频率和反向文档频率
是在每个分片中计算出来的,而不是每个索引中
- 返回的结果中有TF检索词频率,IDF反向文档频率的相关计算
- 输出explain代价是十分昂贵的,只能用于调试,不能用于生产
第一部分是关于计算的总结。告诉了我们 honeymoon 在 tweet 字段中的检索词频率/反向文档频率或TF/IDF, (这里的文档 0 是一个内部的 ID,跟我们没有关系,可以忽略。)
然后它提供了权重是如何计算的细节:
检索词频率:
检索词 `honeymoon` 在这个文档的 `tweet` 字段中的出现次数。
反向文档频率:
检索词 `honeymoon` 在索引上所有文档的 `tweet` 字段中出现的次数。
字段长度准则:
在这个文档中, `tweet` 字段内容的长度 -- 内容越长,值越小。
复杂的查询语句解释也非常复杂,但是包含的内容与上面例子大致相同。 通过这段信息我们可以了解搜索结果是如何产生的。
json形式的explain描述是难以阅读的,可以加上一个参数format=yaml,以yaml格式显示
GET /us/_search?explain=true&format=yaml
{
"query": {
"match_phrase": {
"tweet": "it"
}
}
}
Doc Values介绍
- 当你对一个字段进行排序时,es需要访问每一个文档得到相关的值,倒排索引的检索性能是非常快的
但是在字段值排序时却不是理想的结构
- 在搜索的时候,我们能通过搜索关键词快速得到结果集
- 在排序的时候,我们需要倒排索引里面每个字段值的集合,换句话说,我们需要转置倒排索引
转置结构在其它系统中称为列存储,实际上,它将所有单字段的值存在单数据列中,这使的对其进行操作是十分高效的,例如排序
在es中,Doc Values就是一种列式存储结构,默认情况下,每个字段的Doc Values都是激活的,
Doc Values是在索引时创建的,当字段索引时,es为了能够快速检索,会把字段的值加入到倒排索引中,
同时它也会存储该字段的Doc Values - es中的Doc Value通常被用于一下场景
对一个字段进行排序
对一个字段进行聚合
某些过滤,比如地理位置过滤
某些与字段相关的脚本计算
因为文档值被序列化到磁盘,我们可以依靠操作系统的帮助来快速访问。当 working set 远小于节点的可用内存
系统会自动将所有的文档值保存在内存中,使得其读写十分高速; 当其远大于可用内存
,操作系统会自动把 Doc Values 加载到系统的页缓存中,从而避免了 jvm 堆内存溢出异常。
- 重点:排序发生在索引时建立的平行数据结构中