Elasticsearch 的 Mapping 是什么?dynamic mapping 有哪些坑?
简化版
Mapping 是 Elasticsearch 对文档字段如何存储、索引和查询的结构定义,类似搜索场景里的 schema。dynamic mapping 能自动推断字段类型,方便快速上手,但生产环境容易因为类型推断不符合预期、字段爆炸、日期格式误判等问题导致查询和聚合出错。
详细版
Mapping 主要决定这些事情:
- 字段是什么类型:
text、keyword、long、date、boolean、object、nested等。 - 字段是否被索引:是否能用于查询。
- 字段如何分词:对
text字段使用哪个 analyzer。 - 字段能否用于排序、聚合和精确过滤。
- 是否允许动态新增字段。
dynamic mapping 的优点是省事:写入新字段时,Elasticsearch 会根据值自动创建字段映射。但它的坑也很常见:
- 第一个写入的值决定字段类型,后续不同类型的值可能写入失败。
- 字符串默认可能同时生成
text和.keyword多字段,字段数量会膨胀。 - 日期、数字字符串可能被自动推断成不符合业务预期的类型。
- 日志、JSON 扩展字段过多时,可能触发 mapping explosion。
- 已有字段的核心类型通常不能直接修改,改错后往往要 reindex。
生产建议是:核心字段使用 explicit mapping,未知扩展字段用 dynamic templates 或限制 dynamic 行为。
完整版教学
一、Mapping 为什么重要
Elasticsearch 不是无脑保存 JSON。写入文档时,它要知道每个字段怎么建立索引:
{
"title": "Java 面试题",
"price": 99,
"created_at": "2026-07-19"
}
这些字段可能被映射为:
{
"title": { "type": "text" },
"price": { "type": "long" },
"created_at": { "type": "date" }
}
如果 title 是 text,它会被分析器处理,适合全文检索;如果 price 是 long,它适合范围查询和排序;如果 created_at 是 date,它适合时间范围过滤和时间聚合。
Mapping 设计错了,后面的查询、排序、聚合和性能都会跟着出问题。
二、dynamic mapping 是怎么带来风险的
dynamic mapping 的逻辑是:当新文档出现未知字段时,Elasticsearch 自动识别字段类型并添加到 mapping。
例如第一次写入:
{ "user_id": "10001" }
user_id 可能被识别为字符串相关类型。后来你想按数值范围查:
{ "range": { "user_id": { "gte": 10000 } } }
语义就不对了。反过来,如果第一次写入:
{ "status": 200 }
后续又写:
{ "status": "OK" }
就可能因为类型冲突导致写入失败。
三、为什么已有字段类型通常不能直接改
倒排索引、BKD 树、doc values 等底层结构是按字段类型建立的。一个字段已经按 text 建好了倒排索引,就不能简单把它改成 keyword 或 long,因为底层索引结构和查询语义都不同。
常见修复方式是:
- 创建一个新索引,写好正确 mapping。
- 使用
_reindex把旧数据迁移过去。 - 切换 alias,让业务访问新索引。
- 验证无误后清理旧索引。
这也是为什么生产建索引前要认真设计 mapping,而不是完全依赖自动推断。
四、如何设计更稳的 Mapping
一个常见商品索引可以这样设计:
PUT /products
{
"mappings": {
"dynamic": "strict",
"properties": {
"name": {
"type": "text",
"fields": {
"keyword": { "type": "keyword" }
}
},
"brand": { "type": "keyword" },
"price": { "type": "scaled_float", "scaling_factor": 100 },
"created_at": { "type": "date" },
"tags": { "type": "keyword" }
}
}
}
这里的思路是:
name用text支持全文搜索,同时用name.keyword支持精确排序或聚合。brand、tags用keyword,适合过滤和聚合。- 金额可以用
scaled_float或整数分,避免浮点误差带来的业务歧义。 dynamic: strict可以阻止未知字段悄悄进入 mapping。
如果业务确实有扩展字段,可以使用 dynamic templates 把某些模式的字段统一映射成受控类型。
五、mapping explosion 是什么
mapping explosion 指一个索引里字段数量过多,导致 mapping 体积、集群状态、内存和查询开销迅速膨胀。
典型来源:
- 日志里把任意 JSON 原样打进 Elasticsearch;
- 用户自定义属性没有限制字段名;
- 动态字段包含时间戳、请求 id 等高变化内容;
- 嵌套对象层级过深。
处理思路:
- 对核心字段显式建模;
- 对不需要检索的原始 JSON 放到不可索引字段或对象中;
- 设置字段数量上限;
- 对扩展字段做白名单、前缀规则或扁平化处理;
- 必要时拆分索引。
六、常见误区与追问
| 字段场景 | 推荐 mapping | 原因 |
|---|---|---|
| 标题、正文 | text + 合适 analyzer | 支持全文检索和相关性评分 |
| 状态、枚举、标签 | keyword | 精确过滤、排序、聚合 |
| 金额 | scaled_float 或整数分 | 控制小数精度和业务口径 |
| 扩展 JSON | 受控 dynamic template 或不索引 | 避免字段爆炸 |
第一次写入: {"latency": "12"}
dynamic 推断为字符串类型
后续想 range latency > 100
语义和性能都会变得尴尬,通常只能新建索引并 reindex。
易错点:dynamic mapping 省的是建模时间,花出去的可能是后续 reindex、停机窗口和查询异常成本。
一个数字边界:日志里如果把 HTTP header 原样展开成字段,100 个服务每个服务产生 200 个不同 header 变体,就可能让字段数快速冲到几千甚至更多。字段越多,mapping、cluster state、内存和查询解析成本都会膨胀。
- 误区:Elasticsearch 是 schema-less,所以 mapping 不重要。 ES 接收 JSON 很灵活,但字段如何索引、分词、排序、聚合都由 mapping 决定。
- 误区:dynamic mapping 适合生产核心索引。 核心字段应显式定义,dynamic 更适合探索期或受控扩展字段。
- 误区:字段类型写错可以直接改。 核心类型通常不能原地修改,常见修复路径是新建索引、reindex、切 alias。
- 追问:为什么字符串经常有
text和.keyword? 前者用于全文检索,后者保留完整值用于精确过滤、排序和聚合。 - 追问:mapping explosion 怎么预防? 限制动态字段、使用 dynamic templates、关闭不需要索引的对象、设置字段上限,并对扩展字段做白名单。
- 追问:
dynamic: strict的代价是什么? 未声明字段会写入失败,能保护结构稳定,但需要业务和索引模板同步演进。
七、加强记忆
记住“Mapping 决定字段怎么被索引,dynamic mapping 决定未知字段怎么进来”。面试里先讲 mapping 的作用,再讲自动推断的风险,最后落到生产建议:核心字段显式 mapping,扩展字段受控动态,字段类型改错通常要重建索引。