Disjunction Max Query 又称最佳 best_fields 匹配策略,用来优化当查询关键词出现在多个字段中,以单个字段的最大评分作为文档的最终评分,从而使得匹配结果更加合理
如下的两条例子数据:
docId: 1
title: java python go
content: java scala
docId: 2
title: kubernetes docker
content: java spring python
POST test01/doc/_bulk
{ "index" : { "_id" : "1" } }
{ "title" : "kubernetes docker", "content": "java spring python" }
{ "index" : { "_id" : "2" } }
{ "title" : "java python go", "content": "java scala" }
GET test01/_search?
{
"query": {
"bool": {
"should": [
{"match": {"title": "java spring"}},
{"match": {"content": "java spring"}}
]
}
}
}
结果如下:
{
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 6,
"successful" : 6,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 2,
"max_score" : 0.5753642,
"hits" : [
{
"_index" : "test01",
"_type" : "doc",
"_id" : "2",
"_score" : 0.5753642,
"_source" : {
"title" : "java python go",
"content" : "java scala"
}
},
{
"_index" : "test01",
"_type" : "doc",
"_id" : "1",
"_score" : 0.5753642,
"_source" : {
"title" : "kubernetes docker",
"content" : "java spring python"
}
}
]
}
}
可以看到,两个 doc 的 score 一样,尽管从内容上看 id=1 的 数据更应该排在前面,但默认的排序策略是有可能会导致id=2 的数据排在 id=1 的前面。
在 ES 的默认评分策略下,boolean 查询的score是所有 should 条件匹配到的评分相加,下面简化分析一下得分流程,真实评分会比这个复杂,但大致思路一致:
在 id=1 中数据,由于 title 无命中,但 content 匹配到了 2 个关键词,所以得分为 2.
在 id=2 中数据,其 title 命中 1 个关键词 ,并且其 content 也命中一个关键词,所以最后得分也为 2.
从而得出了最终结果两个 doc 的得分一样
使用 dis_max查询优化匹配机制,采用单字段最大评分,作为最终的 score
GET test01/_search?
{
"query": {
"dis_max": {
"queries": [
{"match": {"title": "java spring"}},
{"match": {"content": "java spring"}}
]
}
}
}
结果如下:
{
"took" : 4,
"timed_out" : false,
"_shards" : {
"total" : 6,
"successful" : 6,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 2,
"max_score" : 0.5753642,
"hits" : [
{
"_index" : "test01",
"_type" : "doc",
"_id" : "1",
"_score" : 0.5753642,
"_source" : {
"title" : "kubernetes docker",
"content" : "java spring python"
}
},
{
"_index" : "test01",
"_type" : "doc",
"_id" : "2",
"_score" : 0.2876821,
"_source" : {
"title" : "java python go",
"content" : "java scala"
}
}
]
}
}
结果已经符合预期了
前面的结果我们看到已经符合预期了,现在如果我们用 dis max 继续查询另一种 case:
GET test01/_search?
{
"query": {
"dis_max": {
"queries": [
{"match": {"title": "python scala"}},
{"match": {"content": "python scala"}}
]
}
}
}
结果如下:
"hits" : [
{
"_index" : "test01",
"_type" : "doc",
"_id" : "2",
"_score" : 0.2876821,
"_source" : {
"title" : "java python go",
"content" : "java scala"
}
},
{
"_index" : "test01",
"_type" : "doc",
"_id" : "1",
"_score" : 0.2876821,
"_source" : {
"title" : "kubernetes docker",
"content" : "java spring python"
}
}
]
可以看到两者的评分又一样了,但从实际来说,我们肯定希望 id = 2 的文档的得分更高的,因为其在多个字段中都有命中,但因为 dis max的匹配评分机制,又导致忽略了其他字段的评分的贡献,这个时候就需要进一步优化了,在 dis max 里面可以使用 tie_breaker 参数来控制,tie_breaker的值默认是 0 ,其设置了tie_breaker参数之后,dis max 的工作原理如下:
改进后的查询语句如下:
GET test01/_search?
{
"query": {
"dis_max": {
"queries": [
{"match": {"title": "python scala"}},
{"match": {"content": "python scala"}}
],
"tie_breaker": 0.4
}
}
}
查询结果:
"hits" : {
"total" : 2,
"max_score" : 0.40275493,
"hits" : [
{
"_index" : "test01",
"_type" : "doc",
"_id" : "2",
"_score" : 0.40275493,
"_source" : {
"title" : "java python go",
"content" : "java scala"
}
},
{
"_index" : "test01",
"_type" : "doc",
"_id" : "1",
"_score" : 0.2876821,
"_source" : {
"title" : "kubernetes docker",
"content" : "java spring python"
}
}
]
}
这样结果就符合我们的预期了
使用dis max 查询可以达到 best_fields 匹配的效果,在某些细分的检索场景下效果更好,但单纯的 dis max 查询会导致忽略其他字段评分贡献,这种一刀切的机制并不是最优的策略,所以需要配合 tie_breaker 参数,来弱化非 best field 子句的评分贡献,从而达到最终的优化效果