这是《Elasticsearch 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 Mapping 决定数据如何被解析、索引、搜索、排序和聚合。很多线上搜索问题的根因不是查询写错,而是 Mapping 设计不合理。
本章覆盖字段类型、动态映射、多字段、嵌套结构、日期数值、元字段、索引参数和 Mapping 治理。
6.1 Mapping 能控制什么
一个字段的 Mapping 通常决定:
- JSON 值如何解析;
- 是否生成倒排索引;
- 使用什么分词器;
- 是否保存 Doc Values;
- 是否参与评分;
- 是否可以聚合和排序;
- 日期和数值格式;
- 超长 keyword 的处理方式。
示例:
PUT /products
{
"mappings": {
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word"
},
"brand": {
"type": "keyword",
"ignore_above": 64
},
"price": {
"type": "scaled_float",
"scaling_factor": 100
},
"description": {
"type": "text",
"norms": false
},
"snapshot": {
"type": "object",
"enabled": false
}
}
}
}
6.2 text 与 keyword
text
text 会被分词,适合全文搜索。
{
"title": {
"type": "text",
"analyzer": "ik_max_word"
}
}
写入“轻薄高性能笔记本电脑”后可能索引出:
轻薄
高性能
笔记本
电脑
适合标题、描述、正文、评论内容、错误消息,不适合状态、订单号、手机号、标签精确匹配和聚合字段。
keyword
keyword 不分词,适合精确匹配、排序和聚合。
{
"order_no": {
"type": "keyword",
"ignore_above": 64
}
}
适合枚举状态、ID 字符串、品牌、类目、标签、主机名、日志级别。
多字段
同一字段同时需要全文搜索和精确聚合时,使用 fields:
{
"title": {
"type": "text",
"analyzer": "ik_max_word",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 128
}
}
}
}
搜索:
{
"match": { "title": "笔记本" }
}
聚合:
{
"terms": { "field": "title.keyword" }
}
6.3 数值类型
| 类型 | 说明 | 适用 |
|---|---|---|
long |
64 位整数 | 订单数、ID、计数 |
integer |
32 位整数 | 状态、数量 |
short |
16 位整数 | 少用 |
byte |
8 位整数 | 少用 |
double |
64 位浮点 | 精度要求不高的金额 |
float |
32 位浮点 | 指标、评分 |
half_float |
半精度浮点 | 低精度指标 |
scaled_float |
带固定缩放因子 | 金额、价格 |
unsigned_long |
无符号 64 位 | 特大整数 |
价格建议:
{
"price": {
"type": "scaled_float",
"scaling_factor": 100
}
}
scaling_factor=100 会把 99.99 存为整数 9999,排序更稳定,也更利于范围查询。
不要把所有数值都定义成 double。数值类型会影响磁盘、排序精度和范围查询效率。
6.4 日期类型
{
"created_at": {
"type": "date",
"format": "strict_date_optional_time||epoch_millis"
}
}
常见输入:
2026-08-25
2026-08-25T10:00:00Z
2026-08-25T18:00:00+08:00
1787642400000
查询:
{
"range": {
"created_at": {
"gte": "now-7d",
"lt": "now"
}
}
}
建议:
- 统一使用 UTC 或明确时区;
- 混合系统时显式声明 format;
- 日志索引名带日期,同时保留
@timestamp字段; - 不要把日期同时作为
text。
6.5 布尔、IP 与范围
布尔:
{ "is_deleted": { "type": "boolean" } }
IP:
{ "client_ip": { "type": "ip" } }
IP 查询:
{
"term": { "client_ip": "192.168.1.10" }
}
范围字段:
{
"valid_period": {
"type": "date_range",
"format": "strict_date_optional_time"
}
}
范围字段适合日程、价格区间、有效期等场景。
6.6 object 与 nested
普通对象:
{
"seller": {
"id": 100,
"name": "Nova 旗舰店",
"city": "Shanghai"
}
}
Mapping:
{
"seller": {
"properties": {
"id": { "type": "long" },
"name": { "type": "keyword" },
"city": { "type": "keyword" }
}
}
}
普通对象数组会被扁平化:
{
"skus": [
{ "color": "red", "size": "S" },
{ "color": "blue", "size": "L" }
]
}
近似变成:
skus.color = [red, blue]
skus.size = [S, L]
查询 color=red AND size=L 会错误匹配,应使用 nested:
{
"skus": {
"type": "nested",
"properties": {
"color": { "type": "keyword" },
"size": { "type": "keyword" },
"price": { "type": "scaled_float", "scaling_factor": 100 }
}
}
}
查询:
{
"nested": {
"path": "skus",
"query": {
"bool": {
"must": [
{ "term": { "skus.color": "red" } },
{ "term": { "skus.size": "S" } }
]
}
}
}
}
nested 的成本:
- 每个 nested 对象是隐藏子文档;
- 数量大会增加索引体积;
- nested query 和聚合更复杂;
- 返回文档时需要重建完整结构。
如果数组对象只展示、不参与条件查询,可以设置:
{
"skus": {
"type": "object",
"enabled": false
}
}
6.7 join 字段
ES 的 join 不是数据库 Join,而是父子文档关系。
PUT /company
{
"mappings": {
"properties": {
"relation": {
"type": "join",
"relations": {
"department": "employee"
}
}
}
}
}
父文档:
PUT /company/_doc/dept_1
{
"name": "Search Platform",
"relation": "department"
}
子文档:
PUT /company/_doc/emp_1?routing=dept_1
{
"name": "Tom",
"relation": {
"name": "employee",
"parent": "dept_1"
}
}
查询:
GET /company/_search
{
"query": {
"has_child": {
"type": "employee",
"query": {
"term": { "name.keyword": "Tom" }
}
}
}
}
限制:
- 父子必须路由到同一分片;
- 查询成本高;
- 更新和重建复杂;
- 大多数场景建议反范式或同步期展开。
6.8 index、doc_values 与 store
index
{
"remark": {
"type": "keyword",
"index": false
}
}
index=false 不能被搜索,但仍会出现在 _source。适合原始报文、大对象快照、只用于展示的扩展字段。
doc_values
{
"price": {
"type": "double",
"doc_values": false
}
}
Doc Values 支持排序和聚合,默认开启。确定不排序、不聚合的字段可以关闭以节省磁盘。
store
{
"title": {
"type": "text",
"store": true
}
}
默认 _source 已保存原始文档,通常不需要 store=true。只有 _source 很大且只需要取少量字段时才考虑。
6.9 norms 与 term_vectors
norms 参与字段长度归一化和 BM25 评分:
{
"description": {
"type": "text",
"norms": false
}
}
关闭后可节省空间,但相关性计算会改变,适合纯召回或过滤型 text 字段。
term_vectors:
{
"content": {
"type": "text",
"term_vector": "with_positions_offsets"
}
}
term_vectors 支持更多高亮和分析能力,但会增加索引体积,应按需开启。
6.10 dynamic mapping
动态映射策略:
{
"mappings": {
"dynamic": "strict",
"properties": {
"title": { "type": "text" }
}
}
}
| 值 | 行为 |
|---|---|
true |
自动添加字段 |
false |
忽略新字段 |
runtime |
新字段作为运行时字段 |
strict |
写入未知字段直接拒绝 |
动态模板:
{
"mappings": {
"dynamic_templates": [
{
"strings_as_keywords": {
"match_mapping_type": "string",
"mapping": {
"type": "keyword",
"ignore_above": 128
}
}
},
{
"ids_as_keyword": {
"match": "*_id",
"mapping": {
"type": "keyword"
}
}
},
{
"times_as_date": {
"match": "*_at",
"mapping": {
"type": "date"
}
}
}
]
}
}
生产建议:
- 主业务索引使用 strict;
- 日志索引起始 Mapping 明确核心字段;
- 未知字段可使用 runtime 或 false;
- 限制字段总数;
- 定期检查字段增长。
6.11 runtime field
运行时字段在查询时计算,不占用索引存储。
PUT /orders/_mapping
{
"runtime": {
"amount_with_tax": {
"type": "double",
"script": {
"source": "emit(doc['amount'].value * 1.13)"
}
}
}
}
查询:
GET /orders/_search
{
"query": {
"range": {
"amount_with_tax": {
"gte": 100
}
}
}
}
优点是不需要重建索引,适合临时字段;缺点是每次查询计算,数据量大时性能差。长期使用的字段应固化到 Mapping 并重新索引。
6.12 Mapping 修改规则
不能直接修改:
- 字段类型;
- 分词器;
index从 false 改 true;doc_values从 false 改 true。
可以新增字段:
PUT /products/_mapping
{
"properties": {
"season": {
"type": "keyword"
}
}
}
如果必须修改已有字段类型,需要创建新索引、Reindex、校验、切换别名。详见第 18 章。
6.13 元字段
| 字段 | 说明 |
|---|---|
_id |
文档 ID |
_source |
原始 JSON |
_routing |
路由 |
_seq_no |
序列号 |
_primary_term |
主分片任期 |
_ignored |
被 ignore_above 忽略的字段 |
建议保留 _source。禁用后更新、高亮、重建索引都会受限。大字段可以使用 index=false 或 enabled=false,不要轻易关闭 _source。
6.14 Mapping 设计流程
推荐步骤:
收集字段来源和业务含义
-> 标记查询类型:搜索/过滤/聚合/排序/展示
-> 选择字段类型
-> 设计多字段结构
-> 确定 dynamic 策略
-> 确定 object/nested
-> 确定日期和金额格式
-> 建立索引模板
-> 写入样本数据
-> 验证查询、排序、聚合
字段设计表模板:
| 字段 | 类型 | 搜索 | 过滤 | 聚合 | 排序 | 说明 |
|---|---|---|---|---|---|---|
| title | text + keyword | 是 | 否 | keyword 可以 | 否 | ik_max_word |
| brand | keyword | 否 | 是 | 是 | 是 | 品牌 |
| price | scaled_float | 否 | 是 | 是 | 是 | 两位小数 |
| status | keyword | 否 | 是 | 是 | 否 | 枚举 |
| description | text | 是 | 否 | 否 | 否 | norms 可关 |
6.15 本章小结
- Mapping 是搜索系统的基础设计,必须在写入前明确;
text用于分词搜索,keyword用于精确过滤、排序和聚合;- 金额优先使用
scaled_float,日期统一格式和时区; - 对象数组需要保留内部关系时使用 nested;
- 父子 join 成本高,优先反范式设计;
- 已有字段类型和分词器不能直接修改,需要重建索引;
- runtime field 灵活但昂贵,长期字段应固化。
6.16 思考题
- 为什么状态字段用 keyword 而不是 text?
title需要“搜索标题”和“按完整标题聚合”两种能力,应如何设计?- 商品 SKU 的颜色和尺码组合查询为什么必须使用 nested?
- 禁用
_source会影响哪些功能? - Mapping 错误后为什么不能原地修改?标准处理流程是什么?