4. 多字段与分词器设计
在 Elasticsearch 中,一个字段最终能否被正确检索,取决于两件事:字段是否被正确映射,以及文本是否被正确的分析器处理。多字段(fields)解决的是“同一份原始值,用不同方式被索引多次”的问题;分析器解决的是“文本如何被切成词项”的问题。两者结合,才能同时满足精确匹配、全文检索、排序聚合等不同需求。
一、多字段:一份数据,多种索引视角
多字段的核心思想是:主字段保留一种分析方式,通过 fields 派生出若干子字段,每个子字段使用不同的 analyzer 或 index 设置。例如一个商品名称,既希望支持分词后的模糊搜索,又希望支持不分词的精确匹配和排序:
PUT /products
{
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "ik_max_word",
"fields": {
"keyword": { "type": "keyword", "ignore_above": 256 },
"pinyin": { "type": "text", "analyzer": "pinyin_analyzer" }
}
}
}
}
}
写入 name = "华为 Mate 60 Pro" 后,实际产生三个索引视角:name 走中文分词,用于全文检索;name.keyword 保留完整原文,用于精确匹配、排序和聚合;name.pinyin 走拼音分析,用于拼音搜索。查询时按目的选择字段:全文检索用 name,精确匹配用 name.keyword,拼音输入用 name.pinyin。需要注意,多字段会成倍增加索引体积,子字段不宜滥用,通常控制在 2~3 个以内。
二、分析器的三段结构
分析器(Analyzer)由三部分组成,按顺序执行:字符过滤器(Character Filter)→ 分词器(Tokenizer)→ 分词过滤器(Token Filter)。字符过滤器在分词前处理原始字符串,如去除 HTML 标签、替换 & 为 and;分词器把字符串切成词项,是唯一必需组件;分词过滤器对切出的词项做进一步加工,如小写化、去停用词、词干提取、加同义词。
三、内置分词器与选择依据
Elasticsearch 内置了多种分词器,选择时主要看语言和切分粒度:
| 分词器 | 切分规则 | 典型场景 |
|---|---|---|
standard | 按 Unicode 文本边界切分,去标点,默认小写 | 英文及多数语言通用 |
whitespace | 仅按空格切分,不去标点 | 需要保留标点的场景 |
keyword | 整段文本作为一个词项,不切分 | 精确值、编码、标签 |
simple | 按非字母字符切分,转小写 | 简单英文文本 |
ik_max_word | 中文最细粒度切分(需插件) | 中文全文检索,召回优先 |
ik_smart | 中文粗粒度切分(需插件) | 中文检索,精度优先 |
中文场景下,standard 会把“数据建模”切成单字,召回高但精度差;ik_max_word 会切成“数据”“建模”“数据建模”等多个词项,兼顾召回与精度。实际项目中,索引时常用 ik_max_word 提高召回,查询时用 ik_smart 提高精度,这是常见搭配。
四、自定义分析器
内置分析器无法满足需求时,通过 settings.analysis 自定义。自定义分析器需要显式声明 type: custom,并指定 tokenizer 和可选的 char_filter、filter:
PUT /articles
{
"settings": {
"analysis": {
"char_filter": {
"my_html": { "type": "html_strip" }
},
"filter": {
"my_stop": { "type": "stop", "stopwords": ["的", "了", "和"] },
"my_syn": { "type": "synonym", "synonyms": ["es, elasticsearch", "电脑, 计算机"] },
"my_stem": { "type": "stemmer", "language": "english" }
},
"analyzer": {
"my_analyzer": {
"type": "custom",
"char_filter": ["my_html"],
"tokenizer": "ik_max_word",
"filter": ["lowercase", "my_stop", "my_syn", "my_stem"]
}
}
}
}
}
定义完成后,可用 _analyze 接口验证效果,这是调试分析器最直接的手段:
GET /articles/_analyze
{
"analyzer": "my_analyzer",
"text": "<p>Elasticsearch 和 电脑</p>"
}
// 返回词项:elasticsearch、计算机(es 被同义词替换,的/了/和 被去除)
五、词干提取与同义词处理
词干提取(Stemming)把词还原为词干,使 running、runs、ran 都能匹配到 run。英文常用 stemmer,中文因无形态变化,通常不做词干提取,而是依赖分词和同义词。词干提取会带来过度归并的风险,例如 universe 与 university 可能被错误归并,需通过测试验证。
同义词(Synonym)用于把不同说法映射到统一词项。同义词过滤器有两种展开方式:synonyms 在索引和查询时都展开,synonyms_path 从文件加载便于维护。同义词配置需注意方向性:"es, elasticsearch" 表示两者等价,而 "es => elasticsearch" 表示单向映射,只把 es 替换为 elasticsearch。同义词文件修改后需要关闭并重新打开索引(_close / _open)才能生效。
_analyze 分别验证索引分析器和查询分析器。常见问题是索引与查询使用了不同分析器,导致“明明有数据却搜不到”。多字段场景下,还要确认查询命中的是哪个子字段。
analyzer 需要重建索引(reindex),因为倒排索引已经按旧分析器生成。生产环境应通过别名(alias)切换,先建新索引、reindex、再切别名,避免停机。
综合来看,多字段与分析器设计应遵循一条主线:先明确每个字段要支持哪些查询方式(全文、精确、排序、聚合),再为每种方式选择合适的分词器和子字段,最后用 _analyze 验证词项是否符合预期。这样设计出的映射,才能在检索精度、召回率和存储成本之间取得平衡。