【线上踩坑分享】使用ES term query时遇到的问题

问题现象

由于对Elasticsearch term query的不了解,导致线上条件过滤出现问题,下面举个例子进行说明:

建立一个索引,其中namekeyword类型,full_nametext类型。

java 复制代码
PUT /test-index
{
  "mappings": {
    "_doc": {
      "properties": {
        "name": {
          "type": "keyword"
        },
        "full_name": {
          "type": "text"
        }
      }
    }
  }
}

插入一条数据

java 复制代码
PUT /test-index/_doc/1
{
  "name":"zhang",
  "full_name":"zhang san"
}

查询name字段

java 复制代码
GET /test-index/_search
{
  "query": {
    "term": {
      "name": "zhang"
    }
  }
}

查询结果如下图所示:

改为查询full_name字段

java 复制代码
GET /test-index/_search
{
  "query": {
    "term": {
      "full_name": "zhang san"
    }
  }
}

没有查到任何结果

但是如果只查询zhang或者san,就可以查询到结果。

java 复制代码
GET /test-index/_search
{
  "query": {
    "term": {
      "full_name": "zhang"
    }
  }
}

问题分析

通过查询官方文档发现,关于term query的使用官方给出了如下3项使用说明:

  1. 避免对text属性的字段使用term query查询。

  2. Elasticsearch会因为分词器的原因改变字段的值,所以如果想要精确匹配text类型的字段将会很难。

  3. 如果查询text类型的字段,建议使用match query

所以,前面的问题很明显和分词有关。

通过如下验证可以发现在标准分词器下,zhang san会被分成zhangsan,所以使用term query查询时,如果直接查询zhang san是查询不到结果的。

java 复制代码
POST _analyze
{
  "analyzer": "standard",
  "text": "zhang san"
}

解决方式

使用match query

按照官方建议,直接使用match query

java 复制代码
GET /test-index/_search
{
  "query": {
    "match": {
      "full_name": "zhang san"
    }
  }
}

term query与match query对比

term query:会直接按照查询内容进行精确匹配,如果分词表中能查到对应的结果则返回。 match query:会对查询的内容先进行分词,然后按照分词的结果与分词表进行匹配,只要有一个匹配到就算匹配成功。 可以这样理解,以分词为查询单位来看,term是精确匹配,而match是模糊匹配。

延伸扩展

关于分词器,我们可以再聊聊,Elasticsearch实际上有多种分词器,同时也支持自定义分词器,所以实际上在不同的分词器下,zhang san可能有不同的拆分方法,就像前面说的,分词的规则如果你掌握不好,就会导致查询的结果和你设想中的结果不匹配,接下来一些案例就让我们来看看默认的分词器standard analyzer会有哪些意想不到的场景!

1. 删除大多数标点符号

java 复制代码
POST _analyze
{
  "analyzer": "standard",
  "text": "hello! zhang san"
}

对比whitespace分词器则可以识别出标点符号

java 复制代码
POST _analyze
{
  "analyzer": "whitespace",
  "text": "hello! zhang san"
}

可以看到hello!并没有被拆分开。

2. 英文大写转小写

这是一个很容易掉坑的地方。

java 复制代码
POST _analyze
{
  "analyzer": "standard",
  "text": "Hello Zhang San"
}

经过standard分词器后,大写的英文字母都被转换成了小写,所以,此时如果你还是按照Hello来查询的话,是不会查询到结果的,这点要特别注意。

3. 超过max_token_length部分单独拆分

max_token_length:最大令牌长度。如果超过此长度,则会被拆分开。默认值为 :255

下面我们自定义一个分词器,并设置max_token_length长度为5

java 复制代码
PUT test-analyzer
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "my_tokenizer"
        }
      },
      "tokenizer": {
        "my_tokenizer": {
          "type": "standard",
          "max_token_length": 5
        }
      }
    }
  }
}

看一下分词效果

java 复制代码
POST test-analyzer/_analyze
{
  "analyzer": "my_analyzer",
  "text": "Hello, Elasticsearch"
}

可以看到,拆分后的每一项长度最多不会超过5

相关推荐
Rust研习社4 分钟前
组合真的优于继承吗?为什么 Rust 和 Go 都拥抱组合舍弃继承?
后端·rust·编程语言
IT_陈寒28 分钟前
JavaScript的闭包把我坑惨了,说好的内存会自动回收呢?
前端·人工智能·后端
CaffeinePro1 小时前
Pydantic深度使用:数据校验、枚举、ORM映射
后端·fastapi
Chenyiax2 小时前
从 Chat 到 Responses:OpenAI API 抽象为什么变了?
后端
MariaH2 小时前
Koa和Express的区别
后端
MariaH2 小时前
Koa框架的使用
后端
luckdewei3 小时前
那个用 passlib 做认证的新同事,上线第一天就把用户密码写进了日志
后端
ping某4 小时前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
JustHappy4 小时前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试
uhakadotcom4 小时前
在python 的 工程化架构中 ,什么是 薄包装器层?
后端·面试·github