想象你走进一家新建的图书馆。馆长要做的第一件事,不是搬书,而是设计一套“藏书档案卡”:书名、作者、价格、出版日期,分别该用什么格式登记?书名的字要能拆成一个个检索标签,ISBN 却必须一字不差地精确保存——这就是 Elasticsearch 里的 Mapping(字段映射)。接着,图书管理员要为每本书手工制作检索标签:把一段话剪成词、擦掉杂质、整理归类——这就是 Analyzer(分词器)。对于没有空格的中文,馆长还专门请来懂中文的 IK 分词员。Mapping 决定“怎么存”,Analyzer 决定“怎么拆”,两者配合,读者才能在几千万本书里一秒锁定想要的那一本。这篇文章,我们就用这座图书馆里发生的故事,把 ES 的 Mapping 与 Analyzer 彻底讲明白。
对搜索工程师来说,Mapping 与 Analyzer 是搜索引擎的地基:配置错了,后面所有搜索优化都是空中楼阁。掌握它们,你才能让搜索既快又准。
可视化演示
动态展示Elasticsearch文本分析管道:从原始文本输入经字符过滤、分词、小写化、停用词过滤到最终索引Token的完整流程,支持切换不同Analyzer。
本节配有交互动效(进入视口后自动加载,约 3KB)。
Standard Analyzer 标准分词器:按词边界切分+小写化+停用词过滤 适合英文文本的通用分析场景 text → tokens → lowercase → stop 默认分析器,覆盖大多数英文检索需求
字段类型定义
馆长设计档案卡时,最头疼的就是“格式”:书名要能拆成标签,所以按“全文”登记;ISBN 要精确到字符,所以按“精确串”登记;价格要能比较大小,所以按“数字”登记;出版日期要能算先后,所以按“日期”登记。每一行栏位用哪种格式,就是字段类型,草率不得。
ES 的 Mapping 就是这张档案卡的蓝本,它把每个字段映射到一种类型。最常用的是 text(全文,会被 Analyzer 分词,支持模糊搜索)与 keyword(精确字符串,不分词,适合过滤、排序、聚合)。数值用 long、integer、double,日期用 date,布尔用 boolean,复杂对象用 object 或 nested。
一个常见需求是“一个字段既要搜索又要精确匹配”。比如城市字段 city,搜索时用 text,分组统计时又需要 keyword。ES 用 multi-fields(多字段)解决——给同一个字段配两个“分身”:
档案卡格式定好了,管理员才有章可循,照着登记不会出错。可如果馆长偷懒,让管理员看着书皮随手猜格式,又会闹出什么乱子?带着这个悬念,我们一起走进仓库,看看动态映射的现场发生了什么。
- 字段 f文档中的一个键,如“标题”、“价格”,它是档案卡上的一行栏位。
- 类型集合花括号里是 ES 支持的候选类型,涵盖文本、数值、日期与对象四大类。
- 映射箭头\mapsto 表示“被指定为”,Mapping 就是这张 字段 → 类型 的对照总表。
{ "properties": { "city": { "type": "text", "fields": { "keyword": { "type": "keyword" } } } } }动态映射机制
新书源源不断送进图书馆,馆长来不及逐本设计档案卡,于是对管理员说:“你看着办。”管理员看到一串数字就登记成“数字”,看到像日期的就登记成“日期”。ES 的 dynamic mapping(动态映射)就是这样一位“随手猜”的管理员。
ES 默认开启 dynamic: true,收到文档后自动推断字段类型,立刻写入索引。推断规则包括:整数 → long,小数 → double,日期格式字符串 → date,其他 → text(并附带 keyword 子字段)。此外还有 date_detection 日期检测和 numeric_detection 数字检测可开关。
但自动猜测有风险:手机号一串数字会被当成 long,前导零和精度全丢;时间戳被当成 text 就没法做范围查询。更麻烦的是,字段类型一旦写入就“定型”了。生产环境通常关闭动态映射:dynamic: false 忽略新字段,dynamic: strict 直接报错,逼着开发人员显式设计。
管理员随手分类省了事,却也埋下隐患。馆长终于意识到:档案卡格式必须有人负责,而真正决定“书好不好找”的,是另一套工序——做检索标签。接下来出场的 Analyzer,才是搜索体验的灵魂。
- 触发条件文档包含 Mapping 未定义的字段,即档案卡上没有登记过的栏位。
- 推断动作按内容形态匹配内置类型规则,数字变数值、日期变 date。
- 结果入库新类型追加进 Mapping,文档同步变成可搜索的数据。
Analyzer组成
为了读者能快速找到书,管理员每周都要给新书做一套检索标签。流水线有三道工序:先用橡皮擦擦掉封面上的灰尘和贴纸(比如 <p> 这类 HTML 残留);再用剪刀把整段文字剪成一个个词;最后在整理台上把标签统一小写、去掉没人搜的“的、了、是”,再贴几张同义词。这套流水线就是 Analyzer。
ES 的 Analyzer 正是由三部分组成:char_filter(字符过滤器,可多个)→ tokenizer(分词器,只能一个)→ token_filter(词条过滤器,可多个)。例如 standard analyzer = 无字符过滤 + standard 分词器 + lowercase 小写过滤 + stop 停用词过滤。通过 _analyze API 可以随时查看一句话被拆成了什么词。
管理员正干得顺手,忽然眼前一黑——一本书上全是中文,没有空格,剪刀无从下手!他只好去请一位懂中文的专家:IK 分词员。这位专家一出手,中文世界的检索难题迎刃而解。
- 字符过滤器处理原文中的杂质,如剥离 HTML 标签、正则替换特殊符号。
- 分词器把连续的文本切成词条,ES 默认按空格与标点切分。
- 词条过滤器逐词加工,如转小写、去停用词、映射同义词。
中文分词IK
英文靠空格天然分词,中文却是连续字符串。“南京市长江大桥”到底是“南京市 / 长江大桥”还是“南京 / 市长 / 江大桥”?馆长请来的 IK 分词员有两种剪法:ik_smart 是“聪明剪”,只剪出最合理的整词,词少而准;ik_max_word 是“贪心剪”,把所有能匹配到的词都剪出来,词多而全。
用标准词典切分“南京市长江大桥”,两种模式的结果一目了然:
中文分词效果还依赖词典。IK 允许自定义词典(在 IKAnalyzer.cfg.xml 中配置 ext.dic),把“周杰伦”“元宇宙”这类专有名词加进去,避免被拆得七零八落;还可以配置 stopword.dic 去掉“的、了”等停用词。
IK 把中文书剪得又快又好。不过管理员很快发现另一个尴尬:读者说“电脑”,可书上的标签贴的是“计算机”——语言不一致,怎么保证双方都能找到对方?答案,就藏在同义词配置里。
- ik_smart贪心做“最长匹配”,一个词吃掉尽可能多的字。
- ik_max_word在每个位置枚举所有可能的词典词,允许重叠。
- 集合对比smart 结果通常是 max_word 结果的子集,粒度不同。
同义词配置
图书管理员恍然大悟:光会分词还不够,读者的口语和书面语常常对不上。于是他在整理台上放了一本“同义词典”:凡是贴了“电脑”标签的书,也自动补一张“计算机”的标签。这样搜索时,无论读者说哪个词,都能把同一批书找出来。
ES 的 synonym token filter 就是这本同义词典。准备好同义词文件 synonyms.txt,每行一条规则,再用逗号或 => 表达关系:
规则写法有讲究:“电脑,计算机 => 电脑” 表示左边两种说法都映射成右边的标准词(单向);“电脑,计算机” 不加箭头表示互相等价(双向)。同义词过滤器属于 token filter,位于分词器之后,作用在词条层面。
有了同义词,搜索召回明显变多,但噪音也随之而来。馆长意识到,光改“拆法”不够,档案卡本身也得好好设计——他决定亲自出马,从源头把规范定死。
- 同义词集合口语说法、缩写、别名放进同一组。
- 归并动作词条过滤阶段把组内写法替换为规范词。
- 查询效果搜索电脑与搜索计算机的召回集合趋于一致。
{ "filter": { "my_syn": { "type": "synonym", "synonyms_path": "analysis/synonyms.txt" } } }自定义映射策略
馆长受够了管理员随手猜格式,决定亲自动手,为每类书制定标准档案卡。虽然前期多花功夫,但一劳永逸:字段类型不会错,分词器不会乱,也不会出现“手机号变数字丢精度”这种事故。
创建索引时显式指定 mappings,每个字段的参数构成一份“档案卡规范”。核心参数包括 type、analyzer、search_analyzer、index、null_value、fields 等:
关键约束:已有字段的类型不可修改。想改,只能新建索引 → reindex 数据 → 切换别名。所以生产环境推荐在索引模板里预定义 Mapping,并对动态映射设限。手动规范虽然前期麻烦,却换来了长期稳定。
馆长把档案卡定得明明白白,检索标签也标准化了。下一步就是终极目标:让读者在几千万本书中,一眼锁定最想要的那一本。一场关于“搜索精度”的战役就此打响。
- type决定字段大类,是 text 还是 keyword。
- analyzer写入时用哪个分词器处理该字段,相当于“入库贴标签”。
- search_analyzer查询时用哪个分词器处理关键词,可与写入时不同。
- index / fields是否索引该字段、是否附加 keyword 分身。
搜索精度优化
搜索不是“找到就行”,而是“找到最相关的”。读者问“大桥”,管理员不能把“南京市长”的书也递上来。精度优化的思路类似:让重要的字段声音更大,让精确的写法不参与模糊匹配,让查询时的拆法更稳。
常用手段:title 等核心字段加权 boost;精确匹配用 keyword 配合 term 查询;只需过滤的字段设 index: false 不建索引,省空间且不干扰搜索;将索引分词器设为 ik_max_word(多存候选词),查询分词器设为 ik_smart(去噪),实现“索引广撒网、查询精准抓”。相关性打分大体由词频、逆文档频率与字段权重决定:
图书馆从“能找”进化到“找得准”,靠的就是 Mapping 与 Analyzer 的默契配合:一个定好存储格式,一个打磨检索标签。无论是电商、日志还是知识库,记住这两件武器,你就能为任何业务搭出又快又准的搜索体验。
- tf 词频词在文档中出现越多,文档被判断为越相关。
- idf 逆文档频率越稀缺的词区分度越高,如“长江大桥”比“书”更有辨识度。
- boost 字段权重title 加权后,标题命中的得分被放大。
AI 应用场景
电商商品搜索
阶段:索引构建与查询阶段
解决的问题:传统数据库 LIKE 查询无法对商品标题做相关性排序,且“笔记本”“笔记本电脑”“laptop“等说法割裂召回。用 ik_max_word 建索引、ik_smart 查索引,配合同义词(”笔记本,笔记本电脑,手提电脑 => 笔记本电脑”),一次搜索即可召回所有表述。
具体案例:某电商把 5000 万商品放进 ES,标题字段 boost 3,价格用 double 做区间过滤,品牌用 keyword 做筛选。用户搜“笔记本”,既能召回“笔记本电脑”,也能按价格排序精确定位,搜索转化率提升约 20%。
日志与可观测平台
阶段:数据写入阶段
解决的问题:日志字段五花八门,动态映射会把“耗时”“时间戳”猜成奇怪类型,导致查询失败或范围查询失效。用 index template 固定 Mapping,dynamic: strict 拒绝未知字段,从源头保证数据形状稳定。
具体案例:某公司每天产生 20 TB 日志,把 request_time 映射为 double、timestamp 映射为 date,其余字段关闭索引(index: false)。写入稳定后,查询耗时从分钟级降到秒级,排障效率大幅提升。
企业文档知识库检索
阶段:查询扩展阶段
解决的问题:专业术语分词错误 + 用户说法多样,导致召回率低。自定义 IK 词典收录“联邦学习”“大模型”等术语,同义词映射“AI、人工智能、智能算法”,让检索更懂行话。
具体案例:企业知识库接入 ES 后,搜“AI“能命中”人工智能“文档,搜”大模型“不会被拆成”大/模型”两个无意义词,内部检索的准确率和用户满意度均明显提升。
Python 演示
# -*- coding: utf-8 -*-"""迷你 Analyzer: 用纯 Python 演示 ES 分词三阶段流水线""" class MiniAnalyzer: """字符过滤器 -> 分词器 -> 词条过滤器""" def __init__(self, char_filters, tokenizer, token_filters): self.char_filters = char_filters self.tokenizer = tokenizer self.token_filters = token_filters def analyze(self, text): out = text for cf in self.char_filters: out = cf.transform(out) tokens = self.tokenizer.cut(out) for tf in self.token_filters: tokens = tf.filter(tokens) return tokens class HtmlStripFilter: """字符过滤器: 剥掉 HTML 标签""" def transform(self, text): return text.replace('<p>', '').replace('</p>', '') class DictTokenizer: """分词器: 基于词典的两种切分模式""" def __init__(self, words, max_word=True): self.words = sorted(words, key=len, reverse=True) self.max_word = max_word def cut(self, text): if not self.max_word: tokens, i = [], 0 while i < len(text): for w in self.words: if text.startswith(w, i): tokens.append(w) i += len(w) break else: tokens.append(text[i]) i += 1 return tokens tokens, seen = [], set() for i in range(len(text)): for w in self.words: if w not in seen and text.startswith(w, i): tokens.append(w) seen.add(w) return tokens class SynonymTokenFilter: """词条过滤器: 同义词扩展""" def __init__(self, mapping): self.mapping = mapping def filter(self, tokens): out = [] for token in tokens: out.append(token) out.extend(self.mapping.get(token, [])) return out if __name__ == '__main__': words = ['南京市', '南京', '市', '长江大桥', '大桥'] smart = MiniAnalyzer( [HtmlStripFilter()], DictTokenizer(words, max_word=False), [] ) print('ik_smart :', smart.analyze('<p>南京市长江大桥</p>')) max_word = MiniAnalyzer( [HtmlStripFilter()], DictTokenizer(words, max_word=True), [] ) print('ik_max_word:', max_word.analyze('<p>南京市长江大桥</p>')) syn = MiniAnalyzer( [], DictTokenizer(words, max_word=True), [SynonymTokenFilter({'长江大桥': ['大桥']})] ) print('同义词扩展 :', syn.analyze('长江大桥'))
for cf in self.char_filters:原文按顺序经过每一道字符过滤器,像橡皮擦依次清理杂质。out = cf.transform(out)每道过滤器把上一道的结果继续清洗,流水线串行。tokens = self.tokenizer.cut(out)分词器把清洗后的文本切成词条列表,这是整条流水线的核心工位。for tf in self.token_filters:词条过滤器逐个加工词条,如小写、去停用词、展开同义词。self.words = sorted(words, key=len, reverse=True)词典按长度倒序排列,保证贪心尽量先匹配最长词。if text.startswith(w, i):判断当前位置是否命中某个词典词,命中就剪下这个词。tokens.append(text[i])词典未命中时保留单字,避免丢字。if w not in seen and text.startswith(w, i):max_word 模式在每个位置枚举候选词,用 seen 去重。out.extend(self.mapping.get(token, []))同义词过滤器把别名追加进结果,实现召回扩展。
常见误区
误区我以为用了 ES 就必须装 IK,所有分词都靠 IK,IK 就是 Analyzer 本身。跟同事聊起 Analyzer 配置,嘴里全是 IK。
正解Analyzer 是“三道工序”的流水线(字符过滤 → 分词 → 词条过滤),IK 只是其中“分词”这一步的中文刀具。英文场景用 standard tokenizer 就够了,IK 只解决中文无空格切分问题。就像借书处中文窗口有一位中文专家,但英文窗口的剪刀本来就是现成的,装不装 IK 取决于数据语言。
为何常见:中文开发者对 IK 的感知最强,很容易把“最抓眼球的零件”当成“整台机器”。
误区我往 keyword 字段里塞了一段话,用 match 查询却搜不到,以为 ES 出了 bug。明明字段里有这些字,为什么查不到?
正解keyword 不分词,整串内容被当成一个词条。match 查询会先把关键词分词再匹配,于是“半个词”永远对不上“整串”。keyword 适合精确值(状态码、ID、品牌名),全文搜索必须用 text。就像 ISBN 只能整串核对,你不能把 978 拆出来当关键词去查书。
为何常见:keyword 和 text 都能“搜”,但搜索机制完全不同,初学者只看到“能搜”,没看到“怎么搜”。
误区索引建好后,我发现 name 字段该用 text,直接 PUT 修改 mapping 就行。结果 ES 报错,说字段已存在无法更新。改个类型而已,为什么这么死板?
正解已有字段类型不可修改,因为数据已按旧类型建好了倒排索引,改类型等于推翻整座索引大厦。正确姿势是:新建索引 → reindex 迁移 → 切换别名,或者一开始就用 index template 把规范定好。
为何常见:关系数据库里 ALTER TABLE 改列类型很常见,惯性思维被带进了 ES。
误区切出来的词越多,搜索召回就越全,所以我建索引和查询全用 ik_max_word。结果搜索反而冒出一堆无关结果,比如搜“大桥”混进了“江大桥”的噪音。
正解max_word 适合“建索引时广撒网”(多存候选词),smart 适合“查询时精准抓”(少而准的词)。查询也用 max_word 会把“市长”这类噪音带进打分。最佳实践是索引 analyzer=ik_max_word、search_analyzer=ik_smart。
为何常见:只看到召回数量,忽略了“相关性质量”和“噪音”这对矛盾。
误区我写了“电脑 => 计算机”,以为搜“计算机”也能召回“电脑”,结果没有。两个词明明是一回事,为什么方向反了就不行?
正解=> 是单向映射:左侧映射到右侧标准词,只保证搜左侧词时扩展出右侧标准词。要双向等价,请使用“电脑,计算机”这种不带箭头的写法。同义词本质是 token filter,方向规则就是它最重要的开关。
为何常见:数学里的"="让人直觉认为对称,但 => 是函数,天然有方向。
面试题
Q1. 在 ES 中,适合存“商品标题”并在搜索时被分词的字段类型是?
- A.keyword 精确串
- B.text 分词全文← 正确答案
- C.integer 整数型
- D.boolean 布尔型
答案:B。text 会走 Analyzer 分词,标题可被拆词匹配;keyword 整串精确匹配,适合 ID、品牌等。选 keyword 就只能在整串完全相等时命中,像 ISBN 不能拆开查一样。
Q2. 一个字段需要“全文搜索 + 精确过滤”双重能力,最合适的做法是?
- A.建立两份独立索引
- B.配置字段的多个分身← 正确答案
- C.字段类型改成 object
- D.直接关闭字段索引能力
答案:B。multi-fields 让同一个字段同时拥有 text 与 keyword 两个“分身”,一个负责拆词搜索,一个负责精确过滤聚合。建两份索引会造成数据冗余,改 object 和关索引都无法兼顾两种能力。
Q3. dynamic: strict 触发的行为是?
- A.忽略新字段
- B.自动推断类型
- C.拒绝写入报错← 正确答案
- D.删除已有字段
答案:C。strict 模式下文档若包含 Mapping 未定义的字段,ES 会直接抛异常拒绝写入,倒逼开发人员显式设计 Mapping。忽略新字段是 dynamic: false 的行为,自动推断是 dynamic: true 的行为。
Q4. Analyzer 三阶段正确的顺序是?
- A.分词器 → 词条过滤 → 字符过滤
- B.字符过滤 → 分词器 → 词条过滤← 正确答案
- C.词条过滤 → 字符过滤 → 分词器
- D.字符过滤 → 词条过滤 → 分词器
答案:B。Analyzer 是固定流水线:先字符过滤清洗原文(擦灰尘),再分词器切词条(动剪刀),最后词条过滤逐词加工(整理台)。顺序颠倒会导致清洗无效或词条加工错位。
Q5. 对“南京市长江大桥”,ik_smart 输出的结果是?
- A.南京市 / 长江大桥← 正确答案
- B.南京 / 市长 / 江大桥
- C.南京 / 市 / 长江大桥
- D.南京市 / 长江 / 大桥
答案:A。ik_smart 是“聪明剪”,选最合理的整词,标准结果为“南京市 / 长江大桥”。B 是歧义错误切法,C、D 是 max_word 风格的部分候选。这题考察两种模式的核心差异:少而准 vs 多而全。
Q6. 下列属于 token filter(词条过滤器)的有?
- A.lowercase 小写过滤← 正确答案
- B.synonym 同义词过滤← 正确答案
- C.stop 停用词过滤← 正确答案
- D.standard 标准分词器
答案:ABC。lowercase、synonym、stop 都在分词之后对词条做加工,属于 token filter。standard 是分词器,负责“动剪刀”切词条,位置在过滤器之前,类比整理台上的三道工序和剪刀手的分工不同。
Q7. 索引已创建后,把 name 字段从 text 改成 keyword 会怎样?
- A.直接修改类型即可生效
- B.报错只能重建索引迁移← 正确答案
- C.新文档写入时自动生效
- D.旧字段会随新数据覆盖
答案:B。字段类型一旦写入不可变,因为数据已按旧类型建好倒排索引。正确做法是新建索引、reindex 数据、切换别名,就像档案卡规则改版后,必须把旧书全部重新登记一遍。
Q8. 以下哪些做法能提升中文搜索精度?
- A.索引用最大词查询用智能切← 正确答案
- B.标题字段增加权重提升← 正确答案
- C.查询关键词也用最大词切
- D.过滤字段关闭索引能力← 正确答案
答案:ABD。索引用 ik_max_word 广撒网、查询用 ik_smart 精准抓,是经典组合;标题 boost 放大核心字段得分;index: false 让纯过滤字段不干扰搜索。C 会把“市长”这类噪音词带进查询打分,反而降低精度。
Q9. 同义词规则中 => 表示什么?
- A.左侧词映射为右侧标准词← 正确答案
- B.右侧词映射为左侧标准词
- C.两个词完全等价互转
- D.规则中的词全部被删除
答案:A。=> 是单向箭头,左侧写法归结为右侧规范词,搜索左侧词时会扩展出右侧标准词;不带箭头的逗号写法才是双向等价。就像档案卡规定“电脑”和“手提电脑”都登记成“电脑”,但没说“电脑”能倒回去叫“手提电脑”。
Q10. 手机号字段被 naive 动态映射成 long 类型,最可能的后果是?
- A.搜索性能反而会下降
- B.号码前导零丢失且被数值化← 正确答案
- C.文档会被直接拒绝写入
- D.字段自动升级成 keyword
答案:B。动态映射按数字形态把手机号推断为 long,前导零被数值化抹掉,号码也失去字符串匹配能力(前缀模糊、精确串过滤)。这提醒我们:像手机号、身份证这类“看起来像数字、其实是标识符”的字段,必须预先 mapping 为 keyword,就像档案卡提前注明“按编号登记,不按数字登记”。