Elasticsearch同分片相同结构文档match查询评分差异问题
问题背景
在Elasticsearch中创建test索引,写入4条文档,写入命令如下:
PUT test/_doc/1 { "tag" : "prove" } PUT test/_doc/2 { "tag" : "prove" } PUT test/_doc/3 { "tag" : "freckle" } PUT test/_doc/4 { "tag" : "freckle" }
执行查询检索tag字段匹配prove或freckle的文档,预期4条文档全部命中,查询语句如下:
GET test/_search { "query": { "bool": { "must": [ { "match": { "tag": "prove freckle" } } ] } } }
查询实际返回4条命中结果,但不同标签的文档评分存在明显差异,返回结果如下:
{ "took" : 950, "timed_out" : false, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : { "value" : 4, "relation" : "eq" }, "max_score" : 0.87546873, "hits" : [ { "_index" : "test", "_type" : "_doc", "_id" : "3", "_score" : 0.87546873, "_source" : { "tag" : "freckle" } }, { "_index" : "test", "_type" : "_doc", "_id" : "4", "_score" : 0.87546873, "_source" : { "tag" : "freckle" } }, { "_index" : "test", "_type" : "_doc", "_id" : "1", "_score" : 0.53899646, "_source" : { "tag" : "prove" } }, { "_index" : "test", "_type" : "_doc", "_id" : "2", "_score" : 0.53899646, "_source" : { "tag" : "prove" } } ] } }
核心疑问
- 所有文档均匹配查询条件,且存储在同一个分片上,所有文档仅包含
tag字段、结构和数据属性完全一致,为何最终返回结果中评分存在明显差异? - 为什么携带
freckle标签的文档评分高于携带prove标签的文档?
答案
评分差异由Elasticsearch默认的BM25相关性算分规则导致,和词项的语义、字符长度没有任何关系。
BM25算分的核心权重项是逆文档频率(IDF):一个词项在索引统计范围内出现的文档数越少(稀有度越高),IDF值越高,匹配该词项的文档获得的基础得分就越高。
你看到的分值完全符合BM25的计算逻辑:
freckle对应的0.87546873分,正好是总文档数统计值为5、词项文档频率为2时的单词语句得分;prove对应的0.53899646分,正好是总文档数统计值为5、词项文档频率为3时的单词语句得分。- 出现统计值和实际写入4条文档、每个词各出现在2条文档的认知偏差,核心原因是Elasticsearch底层Lucene的段存储机制:如果测试过程中重复执行过同ID的
prove文档写入请求(比如重复提交了id=1或id=2的写入命令),旧版本的文档会被标记为删除,但不会立刻从磁盘段中物理清除。段合并触发前,IDF统计会把这部分标记删除的文档计入总文档数和词项文档频率,最终导致两个词的IDF出现差异。等索引触发段合并、标记删除的文档被彻底清理后,两个词的文档频率会回归一致,同结构文档的得分也会完全相同。
补充两个容易混淆的逻辑点:
- 本次使用的
match查询默认采用OR匹配逻辑,只要命中prove、freckle任意一个词就会满足查询条件,每个文档只会累加自身匹配到的单个词的得分,不存在两个词得分叠加的情况。 - 所有文档的
tag字段都只有1个词,字段长度完全一致,BM25的词频归一化、字段长度归一化项对两个词的权重影响完全相同,不会造成得分差异。
内容的提问来源于stack exchange,提问作者Ashish Mishra
相关产品推荐
相关产品推荐

