本文共 4363 字,大约阅读时间需要 14 分钟。
默认情况下,返回的结果是按照 相关性 进行排序的——最相关的文档排在最前。
在这个案例中,通过时间来对 tweets 进行排序是有意义的,最新的 tweets 排在最前。 我们可以使用 sort
参数进行实现:
GET /_search{ "query" : { "bool" : { "filter" : { "term" : { "user_id" : 1 }} } }, "sort": { "date": { "order": "desc" }}}
假定我们想要结合使用 date
和 _score
进行查询,并且匹配的结果首先按照日期排序,然后按照相关性排序:
GET /_search{ "query" : { "bool" : { "must": { "match": { "tweet": "manage text search" }}, "filter" : { "term" : { "user_id" : 2 }} } }, "sort": [ { "date": { "order": "desc" }}, { "_score": { "order": "desc" }} ]}
一种情形是字段有多个值的排序, 需要记住这些值并没有固有的顺序;一个多值的字段仅仅是多个值的包装,这时应该选择哪个进行排序呢?
对于数字或日期,你可以将多值字段减为单值,这可以通过使用 min
、 max
、 avg
或是 sum
排序模式 。 例如你可以按照每个 date
字段中的最早日期进行排序,通过以下方法:
"sort": { "dates": { "order": "asc", "mode": "min" }}
被解析的字符串字段也是多值字段, 但是很少会按照你想要的方式进行排序。如果你想分析一个字符串,如 fine old art
, 这包含 3 项。我们很可能想要按第一项的字母排序,然后按第二项的字母排序,诸如此类,但是 Elasticsearch 在排序过程中没有这样的信息。
你可以使用 min
和 max
排序模式(默认是 min
),但是这会导致排序以 art
或是 old
,任何一个都不是所希望的。
我们真正想要做的是传递一个 单字段 但是却用两种方式索引它。所有的 _core_field 类型 (strings, numbers, Booleans, dates) 接收一个 fields
参数
该参数允许你转化一个简单的映射如:
"tweet": { "type": "string", "analyzer": "english"}
为一个多字段映射如:
"tweet": { "type": "string", "analyzer": "english", "fields": { "raw": { "type": "string", "index": "not_analyzed" } }}
tweet 主字段与之前的一样: 是一个 analyzed 全文字段。
新的 tweet.raw 子字段是 not_analyzed.现在,至少只要我们重新索引了我们的数据,使用 tweet
字段用于搜索,tweet.raw
字段用于排序:
GET /_search{ "query": { "match": { "tweet": "elasticsearch" } }, "sort": "tweet.raw"}
以全文 analyzed
字段排序会消耗大量的内存。
我们曾经讲过,默认情况下,返回结果是按相关性倒序排列的。 但是什么是相关性? 相关性如何计算?
每个文档都有相关性评分,用一个正浮点数字段 _score
来表示 。 _score
的评分越高,相关性越高。
Elasticsearch 的相似度算法被定义为检索词频率/反向文档频率, TF/IDF ,包括以下内容:
检索词频率
检索词在该字段出现的频率?出现频率越高,相关性也越高。 字段中出现过 5 次要比只出现过 1 次的相关性高。
反向文档频率
每个检索词在索引中出现的频率?频率越高,相关性越低。检索词出现在多数文档中会比出现在少数文档中的权重更低。
字段长度准则
字段的长度是多少?长度越长,相关性越低。 检索词出现在一个短的 title 要比同样的词出现在一个长的 content 字段权重更大。
单个查询可以联合使用 TF/IDF 和其他方式,比如短语查询中检索词的距离或模糊查询里的检索词相似度。
当调试一条复杂的查询语句时,想要理解 _score
究竟是如何计算是比较困难的。Elasticsearch 在 每个查询语句中都有一个 explain 参数,将 explain
设为 true
就可以得到更详细的信息。
GET /_search?explain { "query" : { "match" : { "tweet" : "honeymoon" }}}
"_explanation": { //honeymoon 相关性评分计算的总结 "description": "weight(tweet:honeymoon in 0) [PerFieldSimilarity], result of:", "value": 0.076713204, "details": [ { //honeymoon 相关性评分计算的总结 "description": "fieldWeight in 0, product of:", "value": 0.076713204, "details": [ { //检索词频率 "description": "tf(freq=1.0), with freq of:", "value": 1, "details": [ { "description": "termFreq=1.0", "value": 1 } ] }, { //反向文档频率 "description": "idf(docFreq=1, maxDocs=1)", "value": 0.30685282 }, { //字段长度准则 "description": "fieldNorm(doc=0)", "value": 0.25, } ] } ]}
输出 explain
结果代价是十分昂贵的,它只能用作调试工具 。千万不要用于生产环境。
第一部分是关于计算的总结。告诉了我们 honeymoon
在 tweet
字段中的检索词频率/反向文档频率或TF/IDF, (这里的文档 0
是一个内部的 ID,跟我们没有关系,可以忽略。)
然后它提供了权重是如何计算的细节:
检索词频率:
检索词 `honeymoon` 在这个文档的 `tweet` 字段中的出现次数。
反向文档频率:
检索词 `honeymoon` 在索引上所有文档的 `tweet` 字段中出现的次数。
字段长度准则:
在这个文档中, `tweet` 字段内容的长度 -- 内容越长,值越小。
复杂的查询语句解释也非常复杂,但是包含的内容与上面例子大致相同。 通过这段信息我们可以了解搜索结果是如何产生的。
本章的最后一个话题是关于 Elasticsearch
内部的一些运行情况。在这里我们先不介绍新的知识点,所以我们应该意识到,Doc Values
是我们需要反复提到的一个重要话题。
当你对一个字段进行排序时,Elasticsearch
需要访问每个匹配到的文档得到相关的值。倒排索引的检索性能是非常快的,但是在字段值排序时却不是理想的结构。
转置
倒排索引。转置
结构在其他系统中经常被称作 列存储
。实质上,它将所有单字段的值存储在单数据列中,这使得对其进行操作是十分高效的,例如排序。
在 Elasticsearch
中,Doc Values
就是一种列式存储结构,默认情况下每个字段的 Doc Values
都是激活的,Doc Values
是在索引时创建的,当字段索引时,Elasticsearch
为了能够快速检索,会把字段的值加入倒排索引中,同时它也会存储该字段的 Doc Values
。
Elasticsearch
中的 Doc Values
常被应用到以下场景:
因为文档值被序列化到磁盘,我们可以依靠操作系统的帮助来快速访问。当 working set
远小于节点的可用内存,系统会自动将所有的文档值保存在内存中,使得其读写十分高速; 当其远大于可用内存,操作系统会自动把 Doc Values
加载到系统的页缓存中,从而避免了 jvm
堆内存溢出异常。
我们稍后会深入讨论 Doc Values
。现在所有你需要知道的是排序发生在索引时建立的平行数据结构中。
备注:文章参考
转载地址:http://mcyab.baihongyu.com/