Elasticsearch中match_phrase对精确与非精确匹配返回相同分数问题
Elasticsearch精确匹配权重问题排查与解决
问题背景
我有一个分布在6个分片的Elasticsearch企业索引,其中sorted_name字段的索引配置如下:
indexes target_model.sorted_name, type: :keyword, fields: { as_text: { type: :text, norms: false, } }, as: :sorted_name ... def sorted_name "COALESCE(LOWER(TRIM(LEADING FROM COALESCE(companies.name))), '')" end
该字段未使用特殊分词器,且已禁用norms避免字段长度影响评分。我的需求是让精确匹配的结果获得更高权重——比如在sorted_name值为Aliance、Aliance-B2B New York、New Aliance Company的文档中,希望Aliance的得分最高。但使用以下查询语句时,这些文档的得分完全相同,找不到原因:
{ :should => [ { :match_phrase => { :"sorted_name.as_text" => { :query => "альянс", :boost => 5 } } }, { :match => { :"sorted_name.as_text" => { :query => "альянс", :boost => 2 } } }, { :match_phrase_prefix => { :"sorted_name.as_text" => { :query => "альянс" } } } ], :minimum_should_match => 1 }
原因排查
- norms禁用导致评分同质化:关闭norms后,Elasticsearch会跳过字段长度对评分的影响,同时
match/match_phrase这类查询给所有匹配文档的基础分完全一致,仅乘以boost值。如果三个should子句都命中了所有目标文档,总分相加后自然没有差别。 - 未利用keyword字段的精确匹配能力:
sorted_name本身就是keyword类型,天生适合做精确匹配,这才是让完全匹配文档得分领先的最优路径,没必要绕到text子字段做模糊匹配。 - 查询子句匹配范围完全重叠:查询词"альянс"能匹配所有三个文档的text字段内容,三个should子句全部命中每一个文档,最终总分自然无差异。
解决方案
基础优化:优先精确匹配keyword字段
调整查询结构,把精确匹配keyword字段放在首位并赋予最高权重,同时去掉冗余的前缀匹配:
{ :should => [ # 精确匹配整个keyword字段,权重拉满 { :term => { :sorted_name => { :value => "альянс", :boost => 10 } } }, # 短语匹配text字段,权重次之 { :match_phrase => { :"sorted_name.as_text" => { :query => "альянс", :boost => 5 } } }, # 普通匹配text字段,权重最低 { :match => { :"sorted_name.as_text" => { :query => "альянс", :boost => 2 } } } ], :minimum_should_match => 1 }
这样一来,只有sorted_name完全等于"альянс"的文档会命中第一个term查询,直接获得最高的基础分加成,得分会远超其他部分匹配的文档。
进阶优化:给短字段文档额外加分
如果还需要进一步区分部分匹配的文档(比如让Aliance比Aliance-B2B New York得分更高),可以用function_score添加脚本,给内容越短的文档加额外分:
{ :function_score => { :query => { :should => [ { :term => { :sorted_name => { :value => "альянс", :boost => 10 } } }, { :match_phrase => { :"sorted_name.as_text" => { :query => "альянс", :boost => 5 } } }, { :match => { :"sorted_name.as_text" => { :query => "альянс", :boost => 2 } } } ], :minimum_should_match => 1 }, :functions => [ { :script_score => { :script => { :source => "1 / doc['sorted_name'].value.length()" } } } ], :boost_mode => "sum" } }
这个脚本的逻辑是:字段内容长度越短,额外追加的分数越高,确保纯Aliance这类短文档的得分领先于带后缀的长文档。
内容的提问来源于stack exchange,提问作者Artem
相关产品推荐
相关产品推荐

