原作者:善仁
作为一名长期与 Elasticsearch 打交道的引擎研发,我见过太多集群因为一个看似无害的 wildcard 模糊查询而瞬间崩溃。
许多开发者继承了 SQL LIKE %...% 的思维习惯,直接把它搬到 ES 中——在小数据量时没什么大碍,但当文档量上亿时,它会变成拖垮集群的性能黑洞:
- 轻则:错用字段类型,查不准结果,浪费存储
- 重则:暴力扫描,CPU 瞬间打满,集群直接假死
为什么会这样?又该怎么避免?
接下来,我们将深入解析这些问题原因和其解决方案。我们特意在 5000 万级数据量下做了高压测试,用真实数据复刻事故现场,让你一眼看懂问题的根源与规避方案。
对 ES 用户及技术开发爱好者而言,这是一份不可错过的技术文章。
不同查询场景的最佳选型建议
针对不同场景,我们给出以下实战建议:
场景 |
查询模式示例 |
推荐方案 |
核心理由 (基于实测数据) |
任意模糊匹配 |
|
Wildcard 字段 |
综合性价比之王。 存储仅增 20%,无需改代码,性能稳健,支持复杂模式。 |
极致性能狂魔 |
|
Ngram + Constant_Score |
物理极限 (<10ms)。 跳过算分后,性能无敌。代价是存储膨胀和查询代码改造。 |
分词前缀匹配(下拉提示) |
|
Search_As_You_Type |
下拉提升专业选手,空间换时间,可支持超高 qps |
低频词搜索 |
|
Keyword |
keyword 够用。 只要前缀足够长(区分度高),FST 扫描范围小,速度极快(1ms)。 |
特别提醒:选型还要看量级
- 数据量 < 10 万: 默认 keyword 即可,无需过度优化也能实现毫秒级响应。
- 数据量 > 1000 万: 必须慎重选择,否则分分钟线上故障。
一、痛点解析:从“查不到”到“搞挂集群”的三重陷阱
wildcard 的问题不只是“慢”,它首先是一个逻辑陷阱。
陷阱一:分词误用 → 查询结果静默丢失
text 分词导致的“查不到” (The Logic Tra)
- 现象:文档里明明有 "agent 007 bond",但搜 *agent 007* 返回空。
- 原理: text 字段经过分词器(Analyzer)处理,变成了 ["agent", "007", "bond"] 三个独立的词元。wildcard 是去匹配每一个单独的词元,显然没有一个词元长得像 *agent 007*(跨词了)。
Tips:如果怀疑分词存在问题,可使用 _termvectors API 查看磁盘中数据的实际存储情况。你可能会发现,它的存储形式与原文早已大相径庭。
陷阱二:Keyword 模糊查询 → 线性性能雪崩
为了解决“陷阱一”,开发者通常会决定:“改用 keyword 字段吧,它不分词,保留完整字符串。”这时候,业务逻辑通了,但性能噩梦开始了。
- 事故现场: 当你执行 *login* 时,ES 被迫对数百万个长字符串执行逐一扫描。
- 实测验证:在 5000W 数据下,单次查询耗时 13秒,CPU 瞬间打满,集群直接假死。(详见 6.2)
陷阱三:Rewrite 机制限制 → 报错或数据丢失
即使你忍受了 Keyword 的慢,你可能还会遇到更底层的问题。ES 底层的 Rewrite(重写)机制 让你面临两个选择:
- 要么“报错”(Scoring Boolean):如果业务需要算分,ES 必须把匹配词展开为布尔查询。一旦匹配词超过 1024 个(默认限制),查询直接熔断报错:too_many_clauses。
- 要么“丢数据”(Top N):为了不报错,你被迫配置 top_terms_N 。这告诉 ES:“只计算频次最高的 N 个词,剩下的扔掉。”后果就是数据静默消失。
二、实战方案详解(Ngram / Search_as_you_type / Wildcard 配置与查询 DSL)
既然“直接查”是死路,我们必须“将计算压力从查询时转移到索引时”。以下是 3 种方案的完整配置代码。
方案 1:Ngram 索引器(极致性能,代价昂贵)
这是最经典的“空间换时间”方案,通过自定义 Analyzer,在索引时将字符串切分成碎片(如 te,ex,xt...)。
1)索引配置 (Settings & Mapping):
PUT /bench_ngram { "settings": { "analysis": { "tokenizer": { "my_ngram_tokenizer": { "type": "ngram", "min_gram": 2, // 支持搜短词 (如 ID) "max_gram": 3 // 过大会造成存储膨胀严重,2-3 为性价比首选,除非超高 qps,否则不建议更大 } }, "analyzer": { "my_ngram_analyzer": { "tokenizer": "my_ngram_tokenizer" } } } }, "mappings": { "properties": { "sku": { "type": "text", "analyzer": "my_ngram_analyzer" } } } }
2)查询改造 (Query DSL):
注意必须修改查询代码,推荐使用 constant_score 包裹 match (operator: and),这是兼顾准确性与极致性能的最佳实践。但是会有一定假阳,若一定要求准确,则需使用 match_phrase。
GET /bench_ngram/_search { "track_total_hits": false, // 生产环境建议关闭,利用提前终止优化,否则大基数下会非常慢 "query": { "constant_score": { "filter": { "match": { "sku": { "query": "BATCH888", "operator": "and" } } } } } }
Tips: constant_score 换成 bool filter 效果也一样
评价: 速度快到离谱,唯二的缺点是费空间和废代码(需重写 DSL)。
方案 2:search_as_you_type 字段(面向前缀提示的专用类型)
这是 Elasticsearch 官方专为 下拉提示 场景打造的字段类型,它会在索引阶段自动生成多种前缀和短语组合,确保用户在输入的同时即可获得精准、快速的搜索建议。
1)索引配置(Mapping):
PUT /bench_sayt { "mappings": { "properties": { "sku": { "type": "search_as_you_type" } } } }
2)查询方式:
使用 multi_match + bool_prefix 类型。
评价: 它是为“前缀”而生的。虽然也能勉强做中间匹配(通过 stored shingle),但空间占用巨大,且在非前缀场景下性能并不突出。
方案 3:wildcard 字段类型(官方推荐的通用型方案)
wildcard 是 Elasticsearch 7.9+ 专为非结构化文本检索设计的字段类型,本质上是一个自带索引加速器的 keyword 字段。它在底层采用“双重结构”架构,以在通配符查询中兼顾性能与准确性:
- 加速层(Approximation):写入时会自动将字符串切分为 3-gram 片段,存入 倒排索引。查询时,利用倒排链求交能力快速过滤出“可能匹配”的候选文档,将扫描范围从 全量数据 缩小到 极小集合。
- 验证层(Verification):通过 Binary Doc Values 存储完整的原始字符串,对筛选出的候选文档使用 Automaton(有限自动机)进行逐字节精确比对,确保检索结果 100% 准确。
1)索引配置(Mapping):
配置过程非常简单,无需自定义 Analyzer,即可直接在 Mapping 中声明字段类型为 wildcard。
PUT /bench_wildcard { "mappings": { "properties": { "sku": { "type": "wildcard" } } } }
2) 查询方式:
零改造! 继续使用 DSL: wildcard: { "sku": "*BATCH888*" } 。
评价: 唯一推荐的通用解。它是最安全的默认选项。它也许在某些特定 case(如超短前缀)下不如 Ngram 暴力,但它能处理所有情况(包括正则、复杂通配)。查得准、用得爽(零侵入)、存得省。
三、5000W 数据压测对比(性能、存储全维度)
数据胜于空谈。我们在 Serverless 8.17 环境中,写入了 5000 万条高基数且无重复的 UUID 数据,用实测结果向你展示——在架构选型中,你在存储上付出了多少,又在性能上收获了什么。
3.1 keyword 性能退化曲线
为了让大家直观感受都 keyword 的表现,我们测试了不同数据量下的查询耗时。可见,超过 1000w,即使是小基数,也要 1s+,业务已无法使用。
3.2 存储膨胀对比:空间换时间,代价有多大?
我们先观察磁盘占用情况。forcemerge 完成后,不同方案的存储规模差异十分明显。
- Wildcard 效果惊艳: 在支持任意模糊匹配的同时,仅比纯文本 FST 多了 1.2GB 的开销,架构设计极其优秀。
- Ngram 的空间陷阱: 我们在测试中使用了
min:2, max:3的 ngram 配置。但其存储依然膨胀了 73%,达到了 10.9 GB。但这造成了无法搜索单字,若需要搜索单字,需要min:1, 这会造成海量高频词,性能崩塌。便又要max_gram: 10,来进行提速。此时空间直接爆炸。 - SAYT 的空间代价: 42.8 GB 的占用量证明了它就是为“前缀”这一件事不惜血本的设计,绝不适合通用场景。
3.3 中缀查询谁是王者?Wildcard 稳健,Ngram 极致
接下来,我们在 5 QPS 负载下随机执行中缀查询(如 *BATCH888*),对比记录集群在该场景下的 CU 消耗与响应时间,直观体现不同方案的性能差异。
方案 |
平均耗时 |
CU 消耗 |
评述 |
基线 |
6834 ms |
86.5 cu |
集群雪崩: 全表扫描导致 CPU 打满,完全不可用。 |
方案 1 |
5 ms |
0.2 cu |
物理极限: 纯内存倒排索引运算,速度快过 Wildcard 12 倍。 |
方案 3 |
64 ms |
1.03 cu |
工程平衡: 虽然比 Ngram 慢,但 64ms 依然是极佳的实时响应,且不需要改代码。 |
方案 2 |
3416 ms |
40 cu |
不适用: 在非前缀场景下性能极差。 |
Tips:为什么 Ngram 只要 5ms?
在测试中,我们采用了 constant_score 与 track_total_hits: false 的组合策略,跳过了相关性算分和全量命中统计,直接利用倒排索引的位图交集(BitSet Intersection)以及提前终止(Early Termination)机制,将查询过程压缩到了极限。
不过,这种极致性能的实现是有代价的:
1)功能受限: 搜不到单字的短词, 且严禁开启总数统计(否则耗时飙升 1000 倍至 4.4s,见附录 6. 3);
2)代码侵入: 对代码有一定侵入性,需要重写查询 DSL,对于 a*b 这类中间模糊的查询处理复杂。
3.4 前缀查询验证:谁是“前缀之王”?
在测试中,我们分别对比了各方案在 宽泛前缀(匹配大量文档)和 精准前缀(匹配少量文档)两种场景下的表现。
结果显示,SearchAsYouType 不愧为前缀匹配的专业选手,无论前缀长度如何,都能保持稳定的低延迟;而 Wildcard 在处理超长前缀时,由于底层自动机验证过程的开销显著增加,性能会出现明显下滑。
方案 |
短前缀耗时 (16字符, 宽泛) |
长前缀耗时 (32字符, 精准) |
专家点评 |
基线 (Keyword) |
2029 ms |
1 ms |
线性退化。 前缀越短,扫描越慢。只有在精准定位(长前缀)时才是王者。 |
方案 1 (Ngram) |
15 ms |
6 ms |
表现优异。 倒排索引在处理前缀时效率依然很高,仅次于 SAYT。 |
方案 2 (SAYT) |
2 ms |
3 ms |
稳如泰山。 无论前缀长短,耗时恒定极低。下拉提示场景的唯一解。 |
方案 3 (Wildcard) |
16 ms |
8425 ms |
反直觉崩塌。 在极长前缀下,底层自动机构建与验证开销过大,反而比短前缀慢。 |
四、残酷现实:选对了方案,就能高枕无忧吗?
根据上述实测结果,很多开发者的第一反应可能是:“只要将所有字段替换为 Wildcard 类型,就可以彻底解决问题。”
但长周期的生产运行告诉我们,即使在实现层面选择了最优方案,也仍存在两类无法回避的潜在风险——堪称生产环境中的“隐形炸弹”:
4.1 防不住的“手滑”(Human Error):
“你无法保证每一位新入职的同事都熟读开发规范。也许只是一次无心的手滑,在普通的 Keyword 字段上写了一个 *...* 查询——这一行不起眼的代码,足以让你的自建集群在业务高峰期瞬间雪崩,甚至拖垮正常的写入和核心查询。”
4.2 跟不上的“内核”(Technical Debt):
Elasticsearch 社区的演进日新月异。像 Wildcard 这样优秀的底层优化往往依赖最新的内核版本。而自建集群因为担心升级风险,往往只能“锁死”在老版本,眼睁睁看着数倍的性能红利白白流失。
五、阿里云 ES Serverless : 稳定性与先进性的“双重保障”
改变每一位开发者的查询习惯几乎不可能,而我们选择了另一条路:
直接让你的集群拥有“防弹护甲”,自动抵御那些足以击穿性能的高危查询。与自建相比,阿里云 ES Serverless 的架构更健壮、更安全,也更省心。
5.1 内置“智能护栏”,终结单点雪崩
我们不会在后台“魔法般地”自动修改你的业务代码,但可以在风险发生前,阻止高风险查询拖垮整个集群。
阿里云 ES Serverless 内置了企业级的 智能查询限流与熔断机制,能够精准识别那些资源消耗巨大的 “杀手级查询”(Cluster Killers),并进行针对性的限流或熔断处理。
这样,你的集群将始终保持业务连续性——不会因为一条异常 SQL 而全面宕机。高风险查询被隔离控制,正常查询依然可以平稳、流畅地执行。
5.2 内核无感进化,坐享性能红利
基于云原生 Serverless 架构,阿里云实现了内核的 静默无感升级。
无论是 Wildcard 字段底层的实现优化,还是查询执行器的性能改进,你都无需进行任何迁移或重启,即可自动、透明地获得这些优化成果。
这意味着,你可以在 零额外运维成本 的情况下,始终运行在更快、更安全、更强大的版本上,让技术红利直接转化为业务竞争力。
六、压测实录节选(进群获取完整 pdf)
6.1 存储膨胀对比 (5000万数据)
6.2 Keyword 中缀查询耗时随文档数的变化
文档数 |
10w |
50w |
200w |
500w |
1000w |
2000w |
3000w |
4000w |
5000w |
小基数 命中 1% *91-ID* |
15ms |
65ms |
263ms |
635ms |
1255ms |
2467ms |
3728ms |
5328ms |
6273ms |
大基数 命中 100% *25-BA* |
26ms |
118ms |
509ms |
1257ms |
2510ms |
5115ms |
7897ms |
10771ms |
13589ms |
6.3 Ngram 中缀查询耗时变化
查询词长度 |
4 |
8 |
12 |
大基数 命中 100% NG-03 match *25-BA* |
184ms |
1026ms |
2468ms |
大基数 命中 100% NG-01 constant score |
3ms |
2ms |
5ms |
大基数命中 100%+统计总数 NG-01 constant score+track_total_hits |
4414ms |
11214ms |
18428ms |
七、参考资料
官方文档 (User Guides)
- ES 官方文档: Wildcard Query(通配符查询)
- ES 官方文档:
rewrite参数 - ES 官方文档:
indices.query.bool.max_clause_count(最大子句数限制) - ES 官方文档: Ngram Tokenizer(方案1)
- ES 官方文档: Search-as-you-type Field(方案2)
- ES 官方文档: Wildcard Field(方案3)
深入博客(Blog Deep Dives)
- Find strings within strings faster with the Elasticsearch wildcard field
- Elasticsearch Queries, or Term Queries are Really Fast!
- (社区)[Part -1] Search as you type - ashish.one
八、结尾
模糊查询并非洪水猛兽,真正的风险在于 使用场景不匹配 和 字段类型、索引策略选错。在超大数据量的实际业务环境中:
Wildcard → 最稳健的通用方案;
Ngram → 高 QPS 中缀场景下的性能极限选手;
Search_as_you_type → 前缀提示的专业方案。
无论选择哪种方案,最终都取决于你的数据规模、查询模式和运维能力。
获取压测数据 & 加入技术讨论
欢迎加入钉钉群 72335013004,回复“报告”,即可获得本次 5000 万数据压测的完整数据报告。
入群还可获取更多深度避坑指南、内核源码解读及高并发压测报告,与一线研发共同探讨最佳实践。
告别“运维深渊”,把时间还给业务创新
如果你不想因为一次无心的模糊查询而雪崩,可以考虑阿里云 ES Serverless:
- 智能限流与熔断:坏查询关进笼子,好查询按需放行
- 内核无感升级:无需手动迁移享受最新性能优化
这样,你能把时间和精力放在业务创新上,而不是追着故障跑。
立即启用 阿里云 ES Serverless,持续迭代内核与智能性能护栏,让集群始终稳定高效。
更多详情:
阿里云 Elasticsearch Serverless 官网 https://www.aliyun.com/product/es
购买详情:https://common-buy.aliyun.com
最低仅需 ¥160/月,即享 2-12CU 云算力 + 7×24 小时专家团队全程护航,安心专注业务创新。
现在购买1000元节省计划抵扣包,半年期享75折,相当于1333元!一年期享8折,相当于1250元!