elasticsearch 精确匹配搜索(高准确度)

elasticsearch可以说是目前应用非常广泛的搜索引擎了,然而如果想要能够完全的满足检索的需求,还是有些需要注意的点的。

ik分词器,是elasticsearch检索中文时最常用的分词器。其有两种分词模式:

ik_max_word

会将文本做最细粒度的拆分,比如:

中华人民共和国人民大会堂 => 中华人民共和国、中华人民、中华、华人、人民共和国、人民、共和国、大会堂、大会、会堂

ik_smart

会做最粗粒度的拆分,比如:

中华人民共和国人民大会堂 => 中华人民共和国、人民大会堂

常规的最常用的使用方式就是,数据插入存储时用 ik_max_word模式分词,而检索时,用ik_smart模式分词,即:索引时最大化的将文章内容分词,搜索时更精确的搜索到想要的结果。

模糊匹配

mapping:

{"_doc":{"properties":{"content":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart"}}}}

query:

{"bool":{"must":[{"match_phrase":{"content":"\u72fc\u5d3d"}}]}}

该检索能够满足大部分的检索需求,也是最常用的检索方式

模糊匹配&精确匹配

有时候,检索不仅需要支持模糊匹配,还需要支持完全匹配

mapping:

{"_doc":{"properties":{"content":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart","fields":{"keyword":{"type":"keyword","ignore_above":256}}}}}}

通过给字段增加fileds “keyword”, 类型为keyword, 则该fields就可以通过 content.keyword 访问

query:

{"bool":{"must":[{"term":{"content.keyword":"\u6211\u559c\u6b22"}}]}}

通过以上方式则可以实现精准匹配

解决因分词问题导致用一些词检索不到数据的情况

上文我们讲过,索引是用ik_max_word分词,检索时用ik_smart分词,可以说,ik_smart的分词结果是ik_max_word分词结果的一个子集,那么理论上来讲,肯定是能够检索到数据的,那么为什么有时会检索不到呢?

对此官方网站对match_phrase的解释如下:

Like the match query, the match_phrase query first analyzes the query string to produce a list of terms. It then searches for all the terms, but keeps only documents that contain all of the search terms, in the same positions relative to each other.

意思就是说用match_phrase查找时,查找分词器分出的词的位置和要建索引时分出的词的相对位置一样。

例如:

原文:我喜欢一家四口的生活

ik_max_word分词结果

分词位置
0
喜欢1
一家2
3
4
四口5
6
7

检索词: 一家四口

ik_smart分词结果

分词位置
一家0
四口1

从上面可以看出,查找时ik_smart将语句分为了 一家四口 两个词,位置分别为 0 和 1 ,而ik_max_word建索引时,一家四口 的位置分别是 2 和 5,一个位置相邻,一个位置不相邻,在match_phrase看来,这种是匹配的,所以用ik_smart分词短语时无法查到或者查全数据。

为了解决match_phrase这个问题,我们可以辅以使用standard分词器

standard分词器大家都比较熟,针对于汉字就是一个一个分,这种肯定是可以查全的。但一个一个字分的话,每个字对应的文档集合非常多,如果数据量达到了百亿,在求交集,计算距离时,效果非常差。而这里我们可以将其跟ik分词器配合使用,既利用ik分词器的优势,又可以利用standard分词器进行兜底。

但是,此时还有一个问题,就是standard分词只是针对单个字的分词,ik_max_word分出了很多词,如果有“好人”“好人的”这种分词结果,那么搜索“好人”,match_phrase仅能够搜索出“好人”对应的结果,同样符合预期的“好人的”的结果是检索不到的。

针对这种情况,我们利用match_phrase_prefix代替match_phrase,match_phrase_prefix 与 match_phrase查询类似,但是会对最后一个Token在倒排序索引列表中进行通配符搜索。即match_phrase_prefix将会找到“好人”“好人的”对应的结果。

mapping:

{"_doc":{"properties":{"content":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart","fields":{"keyword":{"type":"keyword","ignore_above":256},"standard":{"type":"text","analyzer":"standard"}}}}}}

query:

{"query":{"bool":{"must":[{"bool":{"should":[{"match_phrase_prefix":{"content":"\u6211\u559c\u6b22"}},{"match_phrase_prefix":{"content.standard":"\u6211\u559c\u6b22"}}]}}]}}}

总结

以上就是elasticsearch的检索相关知识,因为es修改mapping相当的麻烦,所以大家在创建索引时,一定要尽可能的将mapping考虑周全,避免之后的修改。

当然,为了以后万一修改的时候减小成本,大家创建索引后,尽量同时创建别名,然后利用别名访问该索引,这样之后就可以通过修改别名指向,从而无缝替换旧索引,不影响线上服务。

附录

如果仅仅是给索引字段增加fields,是不需要重建索引的,可以直接修改mapping:

curl -H "Content-Type: application/json;" -X POST 'http://127.0.0.1:9200/my_index/_doc/_mapping?pretty' -d '
{"_doc":{"properties":{"title":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart","fields":{"keyword":{"type":"keyword","ignore_above":256}}},"content":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart","fields":{"keyword":{"type":"keyword","ignore_above":256}}}}}}
'

修改完mapping后,新的数据就会按照新的mapping来走了,如果想要历史数据也同步新的mapping的话,则利用_update_by_query更新即可:

curl -H "Content-Type: application/json;" -X POST 'http://127.0.0.1:9200/my_index/_update_by_query?conflicts=proceed'

更新时间跟数据量的多少有关,数据越多,则更新所需时间越长。

0 评论
最新
最旧 最多投票
内联反馈
查看所有评论
滚动至顶部