问题现象
由于对Elasticsearch term query
的不了解,导致线上条件过滤出现问题,下面举个例子进行说明:
建立一个索引,其中name
是keyword
类型,full_name
是text
类型。
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项使用说明:
-
避免对
text
属性的字段使用term query
查询。 -
Elasticsearch
会因为分词器的原因改变字段的值,所以如果想要精确匹配text
类型的字段将会很难。 -
如果查询
text
类型的字段,建议使用match query
。
所以,前面的问题很明显和分词有关。
通过如下验证可以发现在标准分词器下,zhang san
会被分成zhang
和san
,所以使用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
。