Elasticsearch 6中带filter的bool查询比无filter查询更慢的问题咨询
首先,我得先确认下你的两个查询逻辑其实是等价的——都是要找出puid=user1且fid=user1-1519812713,同时header或body包含hello的文档。按常理来说,filter应该先帮你缩小数据集,再执行代价极高的*hello*通配符查询,速度应该更快才对。出现反效果,大概率是下面几个原因:
1. Filter缓存没生效(最常见)
Elasticsearch 6里bool filter默认是会被缓存的,但如果是第一次执行这个filter查询,缓存还没生成,或者缓存因为内存不足被淘汰了,就会导致每次都要重新计算filter的结果,反而拖慢速度。
排查&解决:
- 先查下节点的查询缓存状态,执行命令:
看GET _cat/nodes?v&h=name,fielddata.memory_size,query_cache.memory_sizequery_cache.memory_size有没有正常占用,如果一直很低,说明缓存没在工作。 - 可以在filter里明确强制开启缓存(虽然默认是true,但有时候动态生成的查询可能会被跳过缓存):
"filter":[{ "bool":{ "_cache": true, "must":[{ "terms":{"puid":["user1"]}}, {"terms":{"fid":["user1-1519812713"]}} ] } }]
2. 查询执行顺序搞反了
有时候Elasticsearch的查询优化器可能判断失误,先执行了must里的wildcard查询(全量扫描所有文档),再应用filter过滤,这就完全本末倒置了,速度肯定慢。
排查&解决:
- 用
explain参数看两个查询的执行计划:
把两个查询分别传进去,对比执行顺序。如果带filter的查询里先出现了GET /your_index/_validate/query?explainWildcardQuery,说明顺序错了。 - 调整查询结构,把所有条件都放进filter里(因为你不需要打分,filter完全够用):
这样Elasticsearch会优先执行filter里的精确匹配,再处理通配符,而且整个filter会被缓存下来,下次执行直接用缓存结果。{ "query":{ "bool":{ "filter":[ {"terms":{"puid":["user1"]}}, {"terms":{"fid":["user1-1519812713"]}}, { "bool":{ "minimum_should_match":1, "should":[ {"wildcard":{"header":"*hello*"}}, {"wildcard":{"body":"*hello*"}} ] } } ] } } }
3. *hello*通配符本身就是性能杀手
前缀带*的通配符查询(*hello*)是无法利用倒排索引的,Elasticsearch必须扫描每个文档的header和body字段值来匹配,这种全量扫描在数据量大的时候慢到离谱——这才是核心性能瓶颈,filter只是背了锅。
解决办法:
- 如果
header和body是text类型,直接用match查询代替wildcard:
match查询会利用分词后的倒排索引,速度比wildcard快几个数量级,而且语义匹配更准确(比如能匹配"should":[ {"match":{"header":"hello"}}, {"match":{"body":"hello"}} ]Hello、hello world这类变体)。 - 如果必须要做“包含任意位置的hello”的模糊匹配,建议用ngram分词器提前处理字段:
先给索引设置ngram分析器:
然后查询的时候用PUT /your_index { "settings": { "analysis": { "analyzer": { "ngram_analyzer": { "tokenizer": "ngram_tokenizer" } }, "tokenizer": { "ngram_tokenizer": { "type": "ngram", "min_gram": 3, "max_gram": 3 } } } }, "mappings": { "_doc": { "properties": { "header": { "type": "text", "analyzer": "ngram_analyzer", "search_analyzer": "standard" }, "body": { "type": "text", "analyzer": "ngram_analyzer", "search_analyzer": "standard" } } } } }match:
这样Elasticsearch会把字段拆分成3个字符的片段(比如"should":[ {"match":{"header":"hello"}}, {"match":{"body":"hello"}} ]hello拆成hel、ell、llo),查询时直接匹配这些片段,速度和普通match查询一样快。
4. 字段类型设置错误
检查puid和fid的字段类型,如果是text类型,那不带filter的查询里的term查询("term":{"puid":"user1"})可能根本匹配不到多少结果(因为text类型会分词,term是精确匹配分词后的词),而filter里的terms查询可能匹配到更多文档,导致wildcard扫描的范围更大,所以变慢。
解决办法:
把puid和fid改成keyword类型(因为这两个字段是用来精确匹配的):
PUT /your_index/_mapping/_doc { "properties": { "puid": { "type": "keyword" }, "fid": { "type": "keyword" } } }
keyword类型的精确查询性能远高于text,而且filter缓存的效率也更高。
内容的提问来源于stack exchange,提问作者Happy

