背景:我们项目需要对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 关键词查询-实现like查询相关推荐

  1. Elasticsearch 入门(1):基本概念,安装教程,索引的创建,查询,删除,主键查询,修改,添加,聚合查询,条件查询

    Elasticsearch 入门 基本概念 The Elastic Stack, 包括 Elasticsearch.Kibana.Beats 和 Logstash(也称为 ELK Stack).能够安 ...

  2. Elasticsearch(三)——Es搜索(简单使用、全文查询、复合查询)、地理位置查询、特殊查询、聚合操作、桶聚合、管道聚合

    Elasticsearch(三)--Es搜索(简单使用.全文查询.复合查询).地理位置查询.特殊查询.聚合操作.桶聚合.管道聚合 一.Es搜索 这里的 Es 数据博主自己上网找的,为了练习 Es 搜索 ...

  3. elasticsearch 建立索引、增删改查 及简单查询和组合查询的学习笔记

    创建一个索引(数据库) PUT lagou # 索引名称 {"settings": {"index":{"number_of_shards" ...

  4. ElasticSearch高级 (Query DSL查询 bulk批量操作 导入数据 各种查询 实战技巧-优化比重 全量与增量数据同步)

    ElasticSearch高级 01-Query DSL(Domain Specific Language) 1 查询上下文 2 相关度评分:_score 3 元数据:_source 4 Query ...

  5. Elasticsearch的suggest联想提示查询实现

    Elasticsearch的suggest联想提示查询实现 思路 先将关键字在completions 自动补全索引库中查询,获取建议的补全信息 如没有获取到补全信息,可能表示用户输入的关键词有拼写错误 ...

  6. java操作elasticsearch实现前缀查询、wildcard、fuzzy模糊查询、ids查询

    1.前缀查询(prefix) //prefix前缀查询 @Testpublic void test15() throws UnknownHostException {//1.指定es集群 cluste ...

  7. elasticsearch根据某个字段来查询,以及通过时间筛选

    elasticsearch根据某个字段来查询,以及通过时间筛选 String startTime = "2021-06-01"; String endTime = "20 ...

  8. 【Elasticsearch】Elasticsearch的IndexSorting:一种查询性能优化利器

    1.概述 转载:Elasticsearch的IndexSorting:一种查询性能优化利器 前言 前两周写过一篇<基于Lucene查询原理分析Elasticsearch的性能>,在最后留了 ...

  9. java查询oracle数据库_Oracle数据库之java 从Oracle数据库到处数据到Elasticsearch全文检索库进行全文查询...

    本文主要向大家介绍了Oracle数据库之java 从Oracle数据库到处数据到Elasticsearch全文检索库进行全文查询,通过具体的内容向大家展现,希望对大家学习Oracle数据库有所帮助. ...

最新文章

  1. UESTC 1811 Hero Saving Princess
  2. 25行代码AC_蓝桥杯 2017A组省赛第九题 分巧克力(暴力优化)
  3. VS2005 快捷键
  4. 重力加速度换算_中考物理重难点汇总——公式换算大全
  5. java编程式事务_Spring编程式和声明式事务实例讲解
  6. 阿里云云计算 41 阿里云CDN的工作原理
  7. 优质文章推荐(第一期)
  8. 条码打印软件如何添加新字体
  9. iNodeClient 校园网客户端在linux环境下的使用方法
  10. 凹多边形三角剖分算法实现---基于Unity3D
  11. 玩转华为ENSP模拟器系列 | 配置URPF示例
  12. 关于MATLAB调用第三方程序
  13. 假短信截图在线生成器_一个工具箱:汇集200多款实用又好玩的在线工具的神奇百宝箱...
  14. 使用SharedPreferences保存list
  15. UVM搭建 ------ 进阶DIY教程
  16. 妹子图 Spider
  17. 合同法律风险管理 合同签字主体
  18. oracle快速生成序列号,Oracle实现自定义序列号生成
  19. 线程经典实例——吃苹果问题
  20. 【学习笔记】群论入门

热门文章

  1. HR最不喜欢的六类求职者
  2. whl is not a supported wheel on this platform.解决办法
  3. oracle maven依赖
  4. .properties文件加载失败
  5. 深入浅出WPF(8)——数据的绿色通道,Binding(中)
  6. 日语在线翻译网站大全
  7. 医院管理系统mysql课程设计_数据库(课程设计)报告(医院管理系统).doc
  8. php天下第一的梗,DNF中那些怪梗盘点,最后一个已经被99%的玩家遗忘了
  9. TensorRT加速应用
  10. 使用swoole来加速你的laravel应用