相关文章推荐
谦虚好学的钥匙  ·  ColleGo!·  3 月前    · 
重感情的西装  ·  张建华·  1 年前    · 

背景:我们项目需要对es索引里面的一个字段进行关键词(中文+英文+数字混合,中文偏多)搜索,相当于关系型数据库的like操作。要实现这个功能,我们首先想到的方式是用*通配符,但是实际应用场景查询语句会很复杂,*通配符的方式显得不够友好,导致慢查询,甚至内存溢出。

考虑到实际应用场景,一次查询会查询多个字段,我们项目采用query_string query方式,下面只考虑关键词字段。

创建索引 es_test_index

PUT  127.0.0.1:9200/es_test_index
    "order": 0,
    "index_patterns": [
        "es_test_index"
    "settings": {
        "index": {
            "max_result_window": "30000",
            "refresh_interval": "60s",
            "number_of_shards": "3",
            "number_of_replicas": "1"
    "mappings": {
        "logs": {
            "_all": {
                "enabled": false
            "properties": {
                "search_word": {
                    "type": "keyword"
    "profile":true,
    "from":0,
    "size":100,
    "query":{
        "query_string":{
            "query":"search_word:(*中国* NOT *美国* AND *VIP* AND *经济* OR *金融*)",
            "default_operator":"and"

采用*通配符的方式,相当于wildcard query,只是query_string能支持查询多个关键词,并且可以用 AND OR  NOT进行连接,会更加灵活。

"query": { "wildcard" : { "search_word" : "*中国*" }

在我们的应用场景中,关键词前后都有*通配符,这个查询会非常慢,因为该查询需要遍历index里面的每个term。官方文档解释:Matches documents that have fields matching a wildcard expression (not analyzed). Supported wildcards are *, which matches any character sequence (including the empty one), and ?, which matches any single character. Note that this query can be slow, as it needs to iterate over many terms. In order to prevent extremely slow wildcard queries, a wildcard term should not start with one of the wildcards * or ?. 官方文档建议避免以*开头,但是我们要实现全匹配,前后都需要*通配符,可想而知效率是非常慢的。

在我们的实际项目中,我们发现用户有时候会输入很多个关键词,再加上其他的查询条件,单个查询的压力很大,导致了大量的超时。所以,我们决定换种方式实现like查询。

在仔细研究官方文档后,发现可以用standard分词+math_pharse查询实现。

重新创建索引

PUT  127.0.0.1:9200/es_test_index
    "order": 0,
    "index_patterns": [
        "es_test_index_2"
    "settings": {
        "index": {
            "max_result_window": "30000",
            "refresh_interval": "60s",
            "analysis": {
                "analyzer": {
                    "custom_standard": {
                        "type": "custom",
                        "tokenizer": "standard",
                        "char_filter": [
                            "my_char_filter"
                        "filter": "lowercase"
                "char_filter": {
                    "my_char_filter": {
                        "type": "mapping",
                        "mappings": [
                            "· => xxDOT1xx",
                            "+ => xxPLUSxx",
                            "- => xxMINUSxx",
                            "\" => xxQUOTATIONxx",
                            "( => xxLEFTBRACKET1xx",
                            ") => xxRIGHTBRACKET1xx",
                            "& => xxANDxx",
                            "| => xxVERTICALxx",
                            "—=> xxUNDERLINExx",
                            "/=> xxSLASHxx",
                            "!=> xxEXCLAxx",
                            "•=> xxDOT2xx",
                            "【=>xxLEFTBRACKET2xx",
                            "】 => xxRIGHTBRACKET2xx",
                            "`=>xxapostrophexx",
                            ".=>xxDOT3xx",
                            "#=>xxhashtagxx",
                            ",=>xxcommaxx"
            "number_of_shards": "3",
            "number_of_replicas": "1"
    "mappings": {
        "logs": {
            "_all": {
                "enabled": false
            "properties": {
                "search_text": {
                    "analyzer": "custom_standard",
                    "type": "text"
                "search_word": {
                    "type": "keyword"

注意看上面的索引,我创建了两个字段,search_word 跟方式一相同,为了对比两种方式的性能。 search_text :为了使用分析器,将type设置为text ,分析器设置为custom_standard 。

custom_standard组成:

字符过滤器char_filter:采用了mapping char filter 即接受原始文本作为字符流输入,把某些字符(自定义)转换为另外的字符。因为分词器采用了standard分词器,它会去掉大多数的符号,但是关键词搜索的过程可能会带有这些符号,如果去掉的话,会使搜索出来的结果不准确。比如 搜索 红+黄,分词之后 变成 红 黄,那么,搜索出来的结果可能包含 红+黄,红黄 ,而红黄并不是我们想要的。因此,运用字符过滤器,把+转换成字符串xxPLUSxx,那么在分词的时候,+就不会被去掉了。

分词器:standard  该分词器对英文比较友好,对于中文分词会分为单个字这样。

词元过滤器filter:lowercase  把分词过后的词元变为小写。

准备工作就绪,我们准备查询了,现在我们采用match_pharse查询方式。

"from": 0, "size": 100, "query": { "query_string": { "query": "search_text:(\"中国\" NOT \"美国\" AND \"VIP\" AND \"经济\" OR \"金融\")", "default_operator": "and"

我们来看下为什么match_phrase查询能实现关键词左右模糊匹配。

match_phrase 查询首先将查询字符串进行分词(如果不进行其他的参数设置,分词器采用创建索引时search_text字段的分词器custom_standard,如果不明白可以参考官方文档https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis.html),然后对这些词项进行搜索,但只保留那些包含 全部 搜索词项,且 位置 与搜索词项相同的文档。 换句话说,match_phrase查询不仅匹配字,还匹配位置。比如,search_text字段包含的内容是:当代中国正处于高速发展时期。    我们搜索关键词:中国  

索引的时候 search_text经过分词器分为

我们可以用以下api查询分词效果

127.0.0.1:9200/es_test_index_2/_analyze
"analyzer": "custom_standard",
  "text":  "当代中国正处于高速发展时期"

返回结果:

"tokens": [ "token": "当", "start_offset": 0, "end_offset": 1, "type": "<IDEOGRAPHIC>", "position": 0 "token": "代", "start_offset": 1, "end_offset": 2, "type": "<IDEOGRAPHIC>", "position": 1 "token": "中", "start_offset": 2, "end_offset": 3, "type": "<IDEOGRAPHIC>", "position": 2 "token": "国", "start_offset": 3, "end_offset": 4, "type": "<IDEOGRAPHIC>", "position": 3 "token": "正", "start_offset": 4, "end_offset": 5, "type": "<IDEOGRAPHIC>", "position": 4 "token": "处", "start_offset": 5, "end_offset": 6, "type": "<IDEOGRAPHIC>", "position": 5 "token": "于", "start_offset": 6, "end_offset": 7, "type": "<IDEOGRAPHIC>", "position": 6 "token": "高", "start_offset": 7, "end_offset": 8, "type": "<IDEOGRAPHIC>", "position": 7 "token": "速", "start_offset": 8, "end_offset": 9, "type": "<IDEOGRAPHIC>", "position": 8 "token": "发", "start_offset": 9, "end_offset": 10, "type": "<IDEOGRAPHIC>", "position": 9 "token": "展", "start_offset": 10, "end_offset": 11, "type": "<IDEOGRAPHIC>", "position": 10 "token": "时", "start_offset": 11, "end_offset": 12, "type": "<IDEOGRAPHIC>", "position": 11 "token": "期", "start_offset": 12, "end_offset": 13, "type": "<IDEOGRAPHIC>", "position": 12

我们可以看到经过分词之后,search_text会被分为单个的字并且还带有位置信息。位置信息可以被存储在倒排索引中,因此 match_phrase 查询这类对词语位置敏感的查询, 就可以利用位置信息去匹配包含所有查询词项,且各词项顺序也与我们搜索指定一致的文档,中间不夹杂其他词项。

在搜索的时候,关键词“中国”也会经过分词被分为“中”  “国”两个字,然后 match_phrase 查询会在倒排索引中检查是否包含词项“中”和“国”并且“中”出现的位置只比“国”出现的位置大1。这样就刚好可以实现like模糊匹配。

实际上match_phrase查询会比简单的query查询更高,一个 match 查询仅仅是看词条是否存在于倒排索引中,而一个 match_phrase 查询是必须计算并比较多个可能重复词项的位置。Lucene nightly benchmarks 表明一个简单的 term 查询比一个短语查询大约快 10 倍,比邻近查询(有 slop 的短语 查询)大约快 20 倍。当然,这个代价指的是在搜索时而不是索引时。

通常,match_phrase 的额外成本并不像这些数字所暗示的那么吓人。事实上,性能上的差距只是证明一个简单的 term 查询有多快。标准全文数据的短语查询通常在几毫秒内完成,因此实际上都是完全可用,即使是在一个繁忙的集群上。

在某些特定病理案例下,短语查询可能成本太高了,但比较少见。一个典型例子就是DNA序列,在序列里很多同样的词项在很多位置重复出现。在这里使用高 slop 值会到导致位置计算大量增加。

下面我们来看看两种方式的查询效率:

我们用es_test_index_2 索引,里面 search_text是按照方式二定义的,search_word是按照方式一定义的,对两个字段导入相同的数据。

对该索引导入了25302条数据,11.3mb

方式一:*通配符

"profile":true, "from":0, "size":100, "query":{ "query_string":{ "query":"search_word:(NOT *新品* AND *经典* OR *秒杀* NOT *预付*)", "fields": [], "type": "best_fields", "default_operator": "and", "max_determinized_states": 10000, "enable_position_increments": true, "fuzziness": "AUTO", "fuzzy_prefix_length": 0, "fuzzy_max_expansions": 50, "phrase_slop": 0, "escape": false, "auto_generate_synonyms_phrase_query": true, "fuzzy_transpositions": true, "boost": 1

方式二:match_phrase方式

"from": 0, "size": 100, "query": { "query_string": { "query": "search_text:(NOT \"新品\" AND \"经典\" OR \"秒杀\" NOT \"预付\")", "fields": [], "type": "best_fields", "default_operator": "and", "max_determinized_states": 10000, "enable_position_increments": true, "fuzziness": "AUTO", "fuzzy_prefix_length": 0, "fuzzy_max_expansions": 50, "phrase_slop": 0, "escape": false, "auto_generate_synonyms_phrase_query": true, "fuzzy_transpositions": true, "boost": 1

查询结果:

从上面可以看出时间差别还是很大的,当需要查询的关键词很多的时候,优化效果会更好。大家可以自行去验证。

好啦,关键词like查询解决啦。

上述我们用的match_phrase查询属于精确匹配,即必须相邻才能被查出来。如果我们想要查询 “中国经济”,能让包含“中国当代经济”的文档也能查得出来,我们可以用match_phrase查询的参数 slop(默认为0) 来实现:—slop不为0的match_phrase查询称为邻近查询

"from":0, "size":300, "query":{ "match_phrase" : { "search_text" : "query":"中国经济", "slop":2

slop 参数告诉 match_phrase 查询词条相隔多远时仍然能将文档视为匹配 。 相隔多远的意思是为了让查询和文档匹配你需要移动词条多少次? 将slop设置成2 那么 包含“中国当代经济”的文档也能被查询出来。

在query_string query中可以这样写:

"from": 0, "size": 100, "query": { "query_string": { "query": "search_text:(\"中国经济\"~2)", "default_operator": "and"

当然你也可以运用query_string查询的参数 phrase_slop 来设置默认的slop的长度。详情参考https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html

在使用短语查询的时候,会有一些意外的情况出现,比如:

PUT /my_index/groups/1
    "names": [ "John Abraham", "Lincoln Smith"]
PUT /my_index/groups/1
    "names": "John Abraham, Lincoln Smith"

然后我们在运行一个Abraham  Lincoln 短语查询的时候

GET /my_index/groups/_search
    "query": {
        "match_phrase": {
            "names": "Abraham Lincoln"

我们会发现文档会匹配到上述文档,实际上,我们不希望这样的匹配出现,字段names 不管是text数组形式,还是text形式,经过分词之后,都是 John Abraham  Lincoln Smith  ,而 Abraham  Lincoln 属于相邻的,所以短语查询能够匹配到。

在这样的情况下,我们可以这样解决,将这个字段存为数组

DELETE /my_index/groups/ 
PUT /my_index/_mapping/groups 
    "properties": {
        "names": {
            "type":                "string",
            "position_increment_gap": 100

position_increment_gap 设置告诉 Elasticsearch 应该为数组中每个新元素增加当前词条 position 的指定值。 所以现在当我们再索引 names 数组时,会产生如下的结果:

* Position 1: john

* Position 2: abraham

* Position 103: lincoln

* Position 104: smith

现在我们的短语查询可能无法匹配该文档因为 abraham 和 lincoln 之间的距离为 100 。 为了匹配这个文档你必须添加值为 100 的 slop 。position_increment_gap默认是100.

另外,我们也可以在自定义分析器的时候设置该参数。

PUT my_index
  "settings": {
    "analysis": {
      "analyzer": {
        "my_custom_analyzer": {
          "type":      "custom",
          "tokenizer": "standard",
          "char_filter": [
            "html_strip"
          "filter": [
            "lowercase",
            "asciifolding"
         “position_increment_gap":101

参考文档:

https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-query-string-query.html

https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query-phrase.html

https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-custom-analyzer.html

https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-standard-tokenizer.html

https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-mapping-charfilter.html

elasticsearch查询需要实现类似于mysql的like查询效果,例如值为hello中国233的记录,即可以通过中国查询出记录,也可以通过llo查询出记录。 但是elasticsearch查询都是基于分词查询,hello中国233会默认分词为hello、中、国、233。当使用hello查询时可以匹配到该记录,但是使用llo查询时,匹配不到该记录。 由于记录内容分词的结果的粒度... 问题描述: 我们都知道ES针对复杂的多添加组合查询非常强大,也知道通过match可以实现全文检索查询(分词查询),但是如果现在我只需要实现类似mysql中的like全匹配模糊查询,该怎么实现呢? 业务场景: 从content_index表中查询字段content中包含ES的记录。 在关系型数据库中对应的SQL语句: SELECT content FROM content_index WHERE content like '%ES%' 数据准备: ## 删除索引 ## DELETE co 一般来说,各个网站,首页的搜索,都会有进行全文搜索的示例,并且把模糊匹配的多个数据进行标记(高亮),这样便于全局检索关键的数据,便于客户进行浏览。基于此,本文简单介绍这种功能基本java 的 实现。类型ID: 1 线索、2 客户、3 商机、4 联系人、5 售前、6 订单、7 合同,下面是最终的想要的结果,搜索内容代码 1, type 是 类型。由于公司页面此功能隐藏了,本文就以接口调用返回看具体实现了。控制层 controller。// 构建查询条件 & 高亮。逻辑层 service。 近期遇到了使用ES搜索时,有个别的字段需要实现类似于mysql中的Like效果。不跳词,并且可以通配符搜索。 这里以一个商品有商品名需要跳词搜索,而商品的地址就有多地址,而这个多地址是不能跳词的。所以需要用到类似mysql中like搜索的效果。如果搜索地址使用分词搜索的话,搜索广州时,广西,苏州也会被搜索出来,所以需要like来进行搜索。 在ES中搜索时,有很多种模式,下面就是相应关键字对应的搜索模式。 前段时间第一次使用es实现全文索引功能,由于第一次使用,所以只想实现类似数据库 LIKE ‘%关键字%’这样的就可以了。 简单研究了一下es之后,发现将字段类型指定为keyword或查询字段加上.keyword,然后再通过*实例模糊匹配,如下面查询,就可以实现类似LIKE ‘%关键字%’的搜索。 GET /my_index/_doc/_search "query": { "query_s... 实战elasticSearch实现mysql的like搜索效果,并且支侍多个字段的联合搜索效果和订单号后几位的这种mysql无法使用索引的搜索保在ES中创建索引并且如保高效使用索引 小编典典我最终没有使用模糊匹配来解决我的问题,而是使用了ngram。/*** Map - Create a new index with property mapping*/public function map(){$params['index'] = self::INDEX;$params['body']['settings'] = array('index' => array('ana... 在做搜索的时候,下拉联想词的搜索肯定是最常见的一个场景,用户在输入的时候,要自动补全词干,说得简单点,就是以...开头搜索,如果是数据库,一句SQL就很容易实现,但在elasticsearch如何实现呢? 大家可能会立马想到用elasticsearch自带的suggest功能,确实,在一些初级应用场景,特别是数据量比较少的情况下,suggest可以快速简易的解决问题。 在数据量比较大的时... Elasticsearch是一个基于Lucence的搜索服务器。它提供了一个分布式的搜索引擎,基于restful web接口。广泛应用于云计算中,能够实时搜索,具有稳定,可靠,快速的特点。 二.为啥要学习Elasticsearch? 它其实也是一种数据库的优化方案,它的强大之处在于模糊查询。如果是普通数据库查询 like%xxxx%,其实是不走索引的。只要你的数据库的量很大,你的查询肯定会是秒级别的。(es搜索速度很快) 用户输入其实没那么精确或者打错,Elasticsearch却能返回用 EleasticSearch7.X版本实现中文、英文、数字搜索引入依赖客户端连接一些通用的api一些常量和配置类初始化索引库等ES配置信息最后别忘了封装查询的Bean信息 由于基金业务需要接入基金搜索功能,但是网上各种7.X版本的帖子又很少,这里就以自己线上代码,总结了下es相关客户端java代码实现搜索功能。 <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId