ES动态映射(Dynamic Field Mapping)故事版
3.1 自动映射:Dynamic field mapping
field type dynamic true/false boolean 小数 float 数字 long object object 数组 取决于数组中的第一个非空元素的类型 日期格式字符串 date 数字类型字符串 float/long 其他字符串 text + keyword 除了上述字段类型之外,其他类型都必须显示映射,也就是必须手工指定,因为其他类型ES无法自动识别。 好的,让我们用数字村的故事来讲解这个非常重要的概念——动态映射(Dynamic Field Mapping)!
故事:数字村的智能仓库管理员
想象数字村有一个超级智能的仓库管理员,名叫动叔。他的工作是:每当有村民送来新的货物(文档)时,他需要自动判断这个货物应该放在哪个**专属货架(字段类型)**上。
动叔非常聪明,他有一套自己的"识货宝典"(动态映射规则)。但是,这个宝典有时候也会判断失误,所以我们需要了解他的判断逻辑!
动叔的"识货宝典"(动态映射规则)
村民送来的货物 | 动叔的判断 | 放置的货架 | 故事比喻 |
---|---|---|---|
true / false | "这明显是个开关嘛!" | boolean(布尔型) | 是非开关铺 - 只有开/关两种状态 |
3.14 / 2.71 | "带小数点的数字!" | float(浮点型) | 实用量具店 - 处理带精度的测量值 |
42 / 1000 | "这是个整数!" | long(长整型) | 整数杂货铺 - 存放普通整数 |
{"name": "张三"} | "这是个包裹对象!" | object(对象型) | 万能杂物间 - 存放组合物品 |
[1, 2, 3] | "看看数组里第一个是什么..." | 取决于第一个元素的类型 | 看第一个物品决定放哪个货架 |
"2023-10-01" | "这看起来像日期!" | date(日期型) | 标准时间登记处 - 识别日期格式 |
"123" / "3.14" | "数字组成的字符串!" | float/long(数字型) | 当成数字处理,去掉引号 |
"hello world" | "普通文本内容!" | text + keyword(文本+关键字) | 全文检索大师 + 精确身份证登记处 |
详细剧情展开
1. 布尔值的自动识别
// 村民送来:
{"is_student": true, "has_car": false}
// 动叔判断:
"is_student" → boolean类型(是非开关铺)
"has_car" → boolean类型(是非开关铺)
2. 数字的自动识别
// 村民送来:
{"price": 19.99, "quantity": 100}
// 动叔判断:
"price" → float类型(实用量具店)- 因为有小数点
"quantity" → long类型(整数杂货铺)- 因为是整数
3. 对象的自动识别
// 村民送来:
{"user": {"name": "张三", "age": 25}}
// 动叔判断:
"user" → object类型(万能杂物间)
"user.name" → text + keyword(全文+精确)
"user.age" → long类型(整数杂货铺)
4. 数组的"看第一个"规则
// 场景1:数字数组
{"scores": [95, 87, 92]}
// 动叔看第一个元素:95 → long类型
// 场景2:字符串数组
{"tags": ["促销", "新品", "热卖"]}
// 动叔看第一个元素:"促销" → text + keyword类型
// 场景3:空数组
{"empty_list": []}
// 动叔懵逼了:无法判断类型!可能报错或忽略
5. 日期的智能识别
// 村民送来:
{"birthday": "1990-05-15", "create_time": "2023/10/01 14:30:00"}
// 动叔判断:
这两个都符合常见的日期格式 → date类型(标准时间登记处)
6. 数字字符串的特殊处理
// 村民送来:
{"age_str": "25", "price_str": "19.99"}
// 动叔判断:
"age_str" → long类型(去掉引号当成数字25)
"price_str" → float类型(去掉引号当成数字19.99)
7. 文本的"双重保障"
// 村民送来:
{"title": "Elasticsearch教程", "content": "这是一个详细的教学文档..."}
// 动叔判断:
为这两个字段同时创建两种映射:
- text类型:用于全文搜索(可以被分词:"Elasticsearch教程" → "Elasticsearch" + "教程")
- keyword类型:用于精确匹配(必须完全匹配"Elasticsearch教程")
动叔的局限性:必须显式指定的特殊货架
有些特殊的货物,动叔的宝典里没有记录,必须由村民明确说明应该放在哪个货架:
// 这些类型动叔无法自动识别,必须显式映射:
// 1. 地理位置
{"location": "40.7128,-74.0060"} // 动叔会误判为text,但其实是geo_point!
// 2. IP地址
{"ip": "192.168.1.1"} // 动叔会误判为text,但应该是ip类型!
// 3. 半精度浮点数
{"temperature": 25.5} // 动叔会判断为float,但可能是half_float!
// 4. 嵌套类型
{"comments": [{"user": "张三", "content": "好文!"}]} // 动叔会判断为object,但可能是nested!
// 5. 完成建议
{"suggest": "Elasticsearch"} // 动叔会判断为text,但可能是completion!
实战建议:什么时候相信动叔?什么时候亲自指定?
✅ 可以相信动叔的场景(使用动态映射):
- 开发测试阶段:快速验证想法
- 数据结构简单且符合常规模式
- 日志类数据:字段多变,不需要严格约束
❌ 必须亲自指定的场景(使用显式映射):
- 生产环境:需要稳定可靠的映射
- 特殊数据类型:geo_point、ip、nested等
- 性能优化:需要精细控制分词器、索引选项等
- 业务关键字段:如商品ID、用户ID等
显式映射示例:
// 在创建索引时明确指定映射
PUT /my_index
{
"mappings": {
"properties": {
"location": {
"type": "geo_point" // 明确告诉动叔:这是地理坐标!
},
"ip_address": {
"type": "ip" // 明确告诉动叔:这是IP地址!
},
"product_id": {
"type": "keyword" // 明确告诉动叔:这个只要精确匹配!
}
}
}
}
故事总结
数字村的智能管理员动叔虽然很聪明,但他的"自动判断"有时会出错。了解他的判断规则(动态映射规则)非常重要:
- 记住宝典:了解各种数据类型会被自动映射成什么
- 知道局限:明白哪些类型必须显式指定
- 适时干预:在重要场合亲自指定映射,避免意外
这样,你就能在享受动态映射便利的同时,避免掉进映射错误的坑里!🎯
#Dynamic mapping DELETE product_mapping GET product_mapping/_mapping PUT /product_mapping/_doc/1 { "name": "xiaomi phone", "desc": "shouji zhong de zhandouji", "count": 123456, "price": 123.123, "date": "2020-05-20", "isdel": false, "tags": [ "xingjiabi", "fashao", "buka" ] }
GET product_mapping/_search { "query": { "match": { "name.keyword": "xiaomi phone" } } }
让我们详细分析这段 Elasticsearch 操作,理解动态映射的实际应用:
操作流程解析
删除索引(清理环境)
bashDELETE product_mapping
- 删除名为
product_mapping
的索引(如果存在) - 相当于数字村的"拆迁队",清空场地准备重建
- 删除名为
查看映射(此时索引不存在)
bashGET product_mapping/_mapping
- 尝试查看映射,但索引已被删除,会返回错误
- 相当于查看"仓库设计图",但仓库已被拆除
创建文档(触发动态映射)
bashPUT /product_mapping/_doc/1 { "name": "xiaomi phone", "desc": "shouji zhong de zhandouji", "count": 123456, "price": 123.123, "date": "2020-05-20", "isdel": false, "tags": ["xingjiabi", "fashao", "buka"] }
- 动叔(动态映射)开始工作:
name
: 字符串 →text
+keyword
类型desc
: 字符串 →text
+keyword
类型count
: 整数 →long
类型price
: 小数 →float
类型date
: 日期格式字符串 →date
类型isdel
: 布尔值 →boolean
类型tags
: 字符串数组 →text
+keyword
类型
- 动叔(动态映射)开始工作:
执行查询(验证动态映射结果)
bashGET product_mapping/_search { "query": { "match": { "name.keyword": "xiaomi phone" } } }
- 使用
name.keyword
进行精确匹配查询 - 验证动态映射是否创建了
keyword
子字段
- 使用
动态映射的实际效果
执行上述操作后,Elasticsearch 会自动创建如下映射:
{
"product_mapping": {
"mappings": {
"properties": {
"count": {"type": "long"},
"date": {"type": "date"},
"desc": {
"type": "text",
"fields": {"keyword": {"type": "keyword", "ignore_above": 256}}
},
"isdel": {"type": "boolean"},
"name": {
"type": "text",
"fields": {"keyword": {"type": "keyword", "ignore_above": 256}}
},
"price": {"type": "float"},
"tags": {
"type": "text",
"fields": {"keyword": {"type": "keyword", "ignore_above": 256}}
}
}
}
}
}
关键知识点解析
为什么使用
name.keyword
?- 动态映射为字符串字段同时创建了:
text
类型:用于全文搜索(会被分词)keyword
类型:用于精确匹配(不会被分词)
- 查询
"xiaomi phone"
需要完全匹配,所以使用keyword
类型
- 动态映射为字符串字段同时创建了:
ignore_above: 256
的含义- 当字符串长度超过 256 字符时,
keyword
字段将不被索引 - 防止长文本占用过多存储空间
- 可以通过显式映射修改这个值
- 当字符串长度超过 256 字符时,
日期字段的智能识别
"2020-05-20"
被正确识别为日期类型- Elasticsearch 支持多种日期格式的自动识别
动态映射的潜在问题
数字字符串的误判
jsonPUT /product_mapping/_doc/2 { "product_id": "10001" // 会被识别为 long 类型! }
- 解决方案:提前显式声明为
keyword
类型
- 解决方案:提前显式声明为
特殊类型无法自动识别
- 如果需要
geo_point
、ip
等特殊类型,必须显式声明
- 如果需要
字段类型不一致
- 如果后续文档中同一字段出现不同类型数据:json
// 第一个文档 {"price": 199.9} // → float // 第二个文档 {"price": "199.9"} // → text + keyword
- 会导致映射冲突和索引失败
- 如果后续文档中同一字段出现不同类型数据:
最佳实践建议
生产环境使用显式映射
jsonPUT /product_mapping { "mappings": { "properties": { "product_id": {"type": "keyword"}, "name": { "type": "text", "fields": {"raw": {"type": "keyword"}} }, "price": {"type": "scaled_float", "scaling_factor": 100} } } }
控制动态映射行为
jsonPUT /product_mapping { "mappings": { "dynamic": "strict", // 禁止未定义的字段 "properties": {...} } }
重要字段显式声明
- 标识字段(ID、编码等)设为
keyword
- 数值字段明确指定
float
/double
/integer
- 日期字段指定格式
"format": "yyyy-MM-dd"
- 标识字段(ID、编码等)设为
通过理解动态映射机制,你可以更好地利用 Elasticsearch 的灵活性,同时在生产环境中保持数据的一致性和可靠性!
3.2 显示映射 Expllcit field mapping
jsonPUT /product { "mappings": { "properties": { "field": { "mapping_parameter": "parameter_value", ... }, ... } } }
课程演示DSL:
java#手工创建mapping(fields的mapping只能创建,无法修改) #语法 delete /product GET product/_mapping PUT /product { "mappings" : { "properties" : { "count" : { "type" : "long" }, "date" : { "type" : "date" }, "desc" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } }, "isdel" : { "type" : "boolean" }, "name" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } }, "price" : { "type" : "float" }, "tags" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } } } } } PUT /product/_doc/1{ "name": "xiaomi phone", "desc": "shouji zhong de zhandouji", "count": 123456, "price": 123.123, "date": "2020-05-20", "isdel": false }
Elasticsearch 显式映射详解
让我们深入探讨显式映射(Explicit Field Mapping)的概念,并通过实际示例演示其应用。
显式映射的核心概念
显式映射是 Elasticsearch 中手动定义字段类型和属性的方式,与动态映射相对。它提供了对数据结构的精确控制,是生产环境的最佳实践。
基本语法结构
PUT /index_name
{
"mappings": {
"properties": {
"field_name": {
"type": "field_type",
"parameter1": "value1",
"parameter2": "value2"
},
// 更多字段定义...
}
}
}
课程示例解析
1. 创建索引并定义显式映射
PUT /product
{
"mappings": {
"properties": {
"count": {"type": "long"},
"date": {"type": "date"},
"desc": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"isdel": {"type": "boolean"},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"price": {"type": "float"},
"tags": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
2. 插入文档
PUT /product/_doc/1
{
"name": "xiaomi phone",
"desc": "shouji zhong de zhandouji",
"count": 123456,
"price": 123.123,
"date": "2020-05-20",
"isdel": false
}
显式映射的核心优势
精确控制字段类型
- 避免动态映射的误判
- 确保特殊类型正确识别(如geo_point, ip等)
优化存储和性能
- 设置合适的索引选项
- 配置字段压缩方式
- 控制分词器行为
多字段支持
json"name": { "type": "text", // 用于全文搜索 "fields": { "raw": {"type": "keyword"}, // 用于精确匹配 "english": { // 用于特定语言分析 "type": "text", "analyzer": "english" } } }
防止映射爆炸
- 通过显式映射限制字段数量
- 避免动态创建过多字段
常用映射参数详解
1. 核心参数
type
: 字段类型(text, keyword, long, date等)index
: 是否索引(true
/false
)doc_values
: 是否启用列式存储(用于排序和聚合)
2. 文本字段参数
analyzer
: 指定分词器(如standard, ik_smart等)search_analyzer
: 指定搜索时使用的分词器norms
: 是否存储长度信息(影响评分)index_options
: 控制索引内容(docs, freqs, positions, offsets)
3. 数值字段参数
coerce
: 是否尝试转换数据类型(如字符串转数字)ignore_malformed
: 是否忽略格式错误的值scaling_factor
: 缩放因子(用于scaled_float)
4. 日期字段参数
format
: 自定义日期格式(如"yyyy-MM-dd HH:mm:ss")ignore_malformed
: 忽略格式错误的日期
显式映射最佳实践
1. 生产环境必须使用显式映射
PUT /ecommerce
{
"mappings": {
"properties": {
"product_id": {"type": "keyword"},
"product_name": {
"type": "text",
"analyzer": "ik_max_word",
"fields": {
"raw": {"type": "keyword"}
}
},
"price": {"type": "scaled_float", "scaling_factor": 100},
"created_at": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss"
},
"location": {"type": "geo_point"}
}
}
}
2. 动态模板结合显式映射
PUT /logs
{
"mappings": {
"dynamic_templates": [
{
"strings_as_keywords": {
"match_mapping_type": "string",
"mapping": {
"type": "keyword"
}
}
}
],
"properties": {
"@timestamp": {"type": "date"},
"message": {"type": "text"},
"severity": {"type": "keyword"}
}
}
}
3. 映射更新策略
- 已有字段无法修改类型:只能创建新索引并重新导入数据
- 可以添加新字段:使用PUT mapping APIjson
PUT /product/_mapping { "properties": { "new_field": {"type": "integer"} } }
常见问题解决方案
问题1:如何修改已有字段类型?
解决方案:创建新索引 → 定义新映射 → 使用_reindex API迁移数据
POST _reindex
{
"source": {"index": "old_index"},
"dest": {"index": "new_index"}
}
问题2:如何处理动态字段?
解决方案:使用动态模板控制未知字段的行为
"dynamic_templates": [
{
"unindexed_fields": {
"match": "*",
"unmatch": "timestamp",
"mapping": {
"index": false // 不索引未知字段
}
}
}
]
问题3:如何优化大文本字段?
解决方案:禁用norms和doc_values
"description": {
"type": "text",
"norms": false,
"doc_values": false
}
总结
显式映射是 Elasticsearch 数据建模的核心技能:
- 生产环境必须使用:确保数据一致性和性能
- 提供精细控制:字段类型、分词器、索引选项等
- 支持复杂数据结构:对象、嵌套类型、多字段等
- 结合动态模板:灵活处理未知字段
- 需要预先规划:字段类型一旦设置无法直接修改
通过掌握显式映射,您可以构建高性能、可扩展的 Elasticsearch 数据模型,为各种搜索和分析场景提供坚实基础。