背景:

最近公司有个新的小程序项目,主页需要根据公司简称全局搜索并高亮显示,因为公司数据是存放在es中的,所以需要写个查询es的接口,就把之前的代码拷贝过来了,具体生成的DSL语句如下所示:

{
    "from": 0,
    "size": 10,
    "query": {
        "bool": {
            "must": [
                {
                    "term": {
                        "address": {
                            "value": "110000",
                            "boost": 1.0
                        }
                    }
                },
                //以下都是类似的模糊匹配条件,省略一部分
                {
                    "function_score": {
                        "query": {
                            "bool": {
                                "should": [
                                    {
                                        "match_phrase": {
                                            "name": {
                                                "query": "D",
                                                "slop": 0,
                                                "zero_terms_query": "NONE",
                                                "boost": 2.0
                                            }
                                        }
                                    },
                                    {
                                        "match_phrase": {
                                            "name.pinyin": {
                                                "query": "D",
                                                "slop": 0,
                                                "zero_terms_query": "NONE",
                                                "boost": 0.5
                                            }
                                        }
                                    }
                                ]
                            }
                        }
                    }
                }
            ],
            "adjust_pure_negative": true,
            "boost": 1.0
        }
    },
    "sort": [
        {
            "_score": {
                "order": "desc"
            }
        }
    ],
    "highlight": {
        "require_field_match": false,
        "fields": {
            "name": {}
        }
    }
}

问题描述

前端在联调时传入的参数为D,查询公司简称包含D的公司,但是返回的结果中后几条数据没有高亮字段,如下所示

{
    "_score": 28.505045,
    "_source": {
        "full_name": "酩香年华文化传播(北京)有限公司",
        "name": "D老师德语网"
    },
    "highlight": {
        "name": [
            "<em>D</em>老师德语网"
        ]
    }
},
{
    "_score": 8.471922,
    "_source": {
        "full_name": "亚斯兰(北京)国际文化传播有限公司",
        "name": "D-Agent"
    }
}

原因分析

于是查看这个字段的分词情况,发现这个字段的分词器用的是ik_smart。以下是ik_smart对上述两条数据的分词效果。

GET test_analyzer_name/_analyze
{
  "analyzer":"ik_max_word",
  "text":"D老师德语网"
}
结果:
{
  "tokens": [
    {
      "token": "d",
      "start_offset": 0,
      "end_offset": 1,
      "type": "ENGLISH",
      "position": 0
    },
    {
      "token": "老师",
      "start_offset": 1,
      "end_offset": 3,
      "type": "CN_WORD",
      "position": 1
    },
    {
      "token": "德语",
      "start_offset": 3,
      "end_offset": 5,
      "type": "CN_WORD",
      "position": 2
    },
    {
      "token": "网",
      "start_offset": 5,
      "end_offset": 6,
      "type": "CN_CHAR",
      "position": 3
    }
  ]
}

GET test_analyzer_name/_analyze
{
  "analyzer":"ik_smart",
  "text":"D-Agent"
}
结果:
{
  "tokens": [
    {
      "token": "d-agent",
      "start_offset": 0,
      "end_offset": 7,
      "type": "LETTER",
      "position": 0
    }
  ]
}

由此可知,ik_smart分词器对一些特别字符(D-Agent等)分词效果不佳,导致高亮显示时用分词器解析不出正确结果。
但是为什么会返回D-Agent这条数据呢,接着再看查询条件中使用到了拼音去分词,查看mapping中字段的配置,发现没有配置拼音的分词器,于是采用es默认的standard分词器,而它是能够把D分词出来的,并且由于在DSL语句中采用了相关性得分查询,还在query中使用到了should,满足一个查询条件就会被返回,所以结果如上述所示,只是这两条数据的得分相差比较大而已。

为什么高亮显示时采用的是ik_smart分词而不是pinyin的分词?
我的理解:对于pinyin、keyword我更愿意理解为是策略,因为我们在用keyword或者拼音查询时,会在字段名称后面加上这些后缀,例如name.pinyin,对于这种其实是让es走pinyin的策略分词,但实际上这个字段配置的分词器还是ik_smart,以上只是个人理解,仅供参考。

"name": {
            "type": "text",
            "fields": {
              "completion": {
                "type": "text"
              },
              "keyword": {
                "type": "keyword"
              },
              "pinyin": {
                "type": "text"
              }
            },
            "analyzer": "ik_smart"
          }

解决方法

把此字段的分词器由ik_smart改为ik_max_word,即可解决。顺便把拼音分词器加上,以支持拼音查询。

ik分词器分为ik_smart和ik_max_word
ik_max_word :会将文本做最细粒度的拆分,尽可能多的拆分出词语,分词结果可重复
ik_smart:会做最粗粒度的拆分;分词结果不可重复