Elasticsearch 核心技術(二):映射
- 1.什么是映射(Mapping)
- 1.1 元字段(Meta-Fields)
- 1.2 數據類型 vs 映射類型
- 1.2.1 數據類型
- 1.2.2 映射類型
- 2.實際運用案例
- 案例 1:電商產品索引映射
- 案例 2:動態模板設置
- 3.動態映射與靜態映射詳解
- 3.1 動態映射 (Dynamic Mapping)
- 3.1.1 動態映射的三種模式
- 3.1.2 動態映射示例
- 3.2 靜態映射(Explicit Mapping)
- 靜態映射示例
- 3.3 對比
- 3.4 最佳實踐建議
- 4.映射修改詳解
- 4.1 可以修改的內容
- 4.2 不可修改的內容
- 4.3 修改映射的解決方案
- 4.4 案例:將字符串字段從 text 改為 keyword
- 4.4.1 錯誤方式(直接修改會失敗)
- 4.4.2 正確方式(通過重建索引)
- 4.5 注意事項
- 5.注意事項
1.什么是映射(Mapping)
映射是 Elasticsearch 中定義文檔及其包含字段如何存儲和索引的過程。它相當于關系型數據庫中的表結構定義,決定了:
- 每個字段的數據類型
- 字段是否被索引
- 字段的索引方式
- 字段的分析器設置
- 字段的格式(如日期格式)
1.1 元字段(Meta-Fields)
元字段是 Elasticsearch 為每個文檔自動創建的內部字段,用于管理文檔的元數據。常見的元字段包括:
- 標識元字段
_index
:文檔所屬的索引_id
:文檔的唯一 ID
- 文檔源元字段
_source
:存儲原始 JSON 文檔
- 索引元字段
_field_names
:包含非空值的所有字段
- 路由元字段
_routing
:用于將給定文檔路由到指定的分片。
- 其他元字段
_meta
:應用特定的元數據_version
:文檔版本號
例如 Kibana 中自帶的 sample_data_ecommerce
示例數據。
下面框出來的就是元字段信息。
1.2 數據類型 vs 映射類型
1.2.1 數據類型
指字段值的具體類型,如:
- 核心類型:
text
、keyword
、long
、integer
、short
、byte
、double
、float
、boolean
、date
- 復雜類型:
object
、nested
- 地理類型:
geo_point
、geo_shape
- 特殊類型:
ip
、completion
、token_count
1.2.2 映射類型
在 Elasticsearch 7.0 之前,索引可以包含多個類型(類似于表),但 7.0 之后已棄用,每個索引現在只有一個隱式的 _doc
類型。
2.實際運用案例
案例 1:電商產品索引映射
PUT /products
{"mappings": {"properties": {"name": { "type": "text", "analyzer": "ik_max_word" },"price": { "type": "double" },"description": { "type": "text" },"category": { "type": "keyword" },"tags": { "type": "keyword" },"created_at": { "type": "date", "format": "yyyy-MM-dd HH:mm:ss" },"specs": { "type": "object" },"location": { "type": "geo_point" }}}
}
案例 2:動態模板設置
PUT /my_index
{"mappings": {"dynamic_templates": [{"strings_as_keywords": {"match_mapping_type": "string","mapping": {"type": "keyword"}}}]}
}
3.動態映射與靜態映射詳解
3.1 動態映射 (Dynamic Mapping)
動態映射是 Elasticsearch 自動檢測和創建字段映射的能力。當索引一個新文檔時,如果包含未定義的字段,Elasticsearch 會根據字段值自動推斷數據類型并創建映射。
3.1.1 動態映射的三種模式
true
(默認):自動添加新字段false
:忽略新字段(不索引但會存儲在_source
中)strict
:拒絕包含新字段的文檔(拋出異常)
3.1.2 動態映射示例
# 創建索引時不定義映射(使用默認動態映射)
PUT /dynamic_index# 插入包含新字段的文檔
POST /dynamic_index/_doc/1
{"name": "John Doe", # 自動識別為text字段"age": 30, # 自動識別為long"birth_date": "1990-01-01", # 自動識別為date"is_active": true, # 自動識別為boolean"salary": 5000.50, # 自動識別為float"tags": ["tech", "sports"], # 自動識別為text數組"address": { # 自動識別為object"street": "123 Main St","city": "New York"}
}
查看自動生成的映射
GET /dynamic_index/_mapping# 返回結果示例:
{"dynamic_index": {"mappings": {"properties": {"address": {"properties": {"city": { "type": "text", "fields": { "keyword": { "type": "keyword" } } },"street": { "type": "text", "fields": { "keyword": { "type": "keyword" } } }}},"age": { "type": "long" },"birth_date": { "type": "date" },"is_active": { "type": "boolean" },"name": { "type": "text", "fields": { "keyword": { "type": "keyword" } } },"salary": { "type": "float" },"tags": { "type": "text", "fields": { "keyword": { "type": "keyword" } } }}}}
}
3.2 靜態映射(Explicit Mapping)
靜態映射是手動預定義索引的字段結構和數據類型,在創建索引時明確指定每個字段的類型和屬性。
靜態映射示例
# 創建索引時明確定義映射
PUT /static_index
{"mappings": {"dynamic": "strict", # 嚴格模式,禁止未定義的字段"properties": {"name": {"type": "text","analyzer": "standard","fields": {"keyword": { "type": "keyword" }}},"age": { "type": "integer" },"birth_date": {"type": "date","format": "yyyy-MM-dd||epoch_millis"},"is_active": { "type": "boolean" },"salary": { "type": "scaled_float", "scaling_factor": 100 },"tags": {"type": "keyword"},"address": {"type": "object","properties": {"street": { "type": "keyword" },"city": { "type": "keyword" },"coordinates": { "type": "geo_point" }}},"comments": {"type": "nested","properties": {"user": { "type": "keyword" },"message": { "type": "text" },"rating": { "type": "byte" }}}}}
}
嘗試插入未定義字段的文檔
POST /static_index/_doc/1
{"name": "Jane Smith","age": 28,"new_field": "test" # 將拋出異常,因為dynamic=strict
}# 錯誤響應:
{"error": {"root_cause": [{"type": "strict_dynamic_mapping_exception","reason": "mapping set to strict, dynamic introduction of [new_field] within [_doc] is not allowed"}]},"status": 400
}
3.3 對比
特性 | 動態映射 | 靜態映射 |
---|---|---|
字段創建方式 | 自動推斷 | 手動預定義 |
靈活性 | 高 | 低 |
可控性 | 低 | 高 |
適合場景 | 開發初期、數據結構不確定 | 生產環境、數據結構穩定 |
性能影響 | 可能產生不理想的映射 | 可優化映射提升性能 |
維護成本 | 低(初期)高(后期整理) | 高(前期)低(后期) |
數據一致性 | 可能不一致 | 高度一致 |
3.4 最佳實踐建議
-
開發階段:可以使用動態映射快速原型開發。
PUT /dev_index {"mappings": {"dynamic": true} }
-
過渡階段:使用動態模板(
dynamic templates
)控制自動映射。PUT /transition_index {"mappings": {"dynamic_templates": [{"strings_as_keywords": {"match_mapping_type": "string","mapping": {"type": "keyword"}}}]} }
-
生產環境:推薦使用靜態映射。
PUT /prod_index {"mappings": {"dynamic": "strict","properties": {// 明確定義所有字段}} }
-
混合使用:可以結合兩者優勢。
PUT /hybrid_index {"mappings": {"dynamic": "false", # 不自動索引新字段,但存儲在_source"properties": {// 明確定義已知字段}} }
通過合理選擇映射策略,可以在靈活性和可控性之間取得平衡,為不同階段的業務需求提供最合適的解決方案。
4.映射修改詳解
在 Elasticsearch 中,映射創建后是可以修改的,但有重要的限制和注意事項。
4.1 可以修改的內容
-
添加新字段:任何時候都可以向現有映射添加新字段。
PUT /my_index/_mapping {"properties": {"new_field": { "type": "text" }} }
-
修改某些字段屬性:
- 可以更新
fields
多字段設置 - 可以修改
analyzer
、search_analyzer
等分析相關設置 - 可以修改
ignore_above
(keyword 字段) - 可以修改
null_value
設置
- 可以更新
-
動態映射規則:可以更新動態模板(dynamic templates)
4.2 不可修改的內容
- 字段數據類型:不能更改已有字段的數據類型。
- 例如:不能將
text
改為keyword
,不能將long
改為integer
。
- 例如:不能將
- 已索引的字段:不能更改已索引字段的基本結構。
- 例如:不能將單字段改為多字段。
- 字段名稱:不能直接重命名字段。
4.3 修改映射的解決方案
當需要做不允許的修改時,可以考慮以下方案:
-
重建索引(Reindex)
- 創建新索引,定義新映射。
- 使用 Reindex API 將數據從舊索引復制到新索引。
- 示例:
POST _reindex {"source": { "index": "old_index" },"dest": { "index": "new_index" } }
-
使用別名(Alias)
- 創建指向新索引的別名。
- 無縫切換應用查詢到新索引。
- 示例:
POST _aliases {"actions": [{ "remove": { "index": "old_index", "alias": "my_alias" } },{ "add": { "index": "new_index", "alias": "my_alias" } }] }
-
多字段(Multi-fields)
- 為字段添加不同數據類型的多字段版本。
- 示例:
PUT /my_index/_mapping {"properties": {"my_field": {"type": "text","fields": {"keyword": { "type": "keyword" }}}} }
4.4 案例:將字符串字段從 text 改為 keyword
4.4.1 錯誤方式(直接修改會失敗)
PUT /my_index/_mapping
{"properties": {"category": { "type": "keyword" } // 如果原先是text,這會報錯}
}
4.4.2 正確方式(通過重建索引)
// 1. 創建新索引
PUT /my_index_v2
{"mappings": {"properties": {"category": { "type": "keyword" }}}
}// 2. 重新索引數據
POST _reindex
{"source": { "index": "my_index" },"dest": { "index": "my_index_v2" }
}// 3. 切換別名
POST _aliases
{"actions": [{ "remove": { "index": "my_index", "alias": "products" } },{ "add": { "index": "my_index_v2", "alias": "products" } }]
}
4.5 注意事項
- 生產環境謹慎操作:映射更改可能影響現有查詢和應用程序。
- 停機時間考慮:重建大索引可能需要時間,規劃好維護窗口。
- 版本兼容性:Elasticsearch 不同版本對映射修改的支持可能不同。
- 監控影響:修改后監控集群性能和查詢結果。
- 備份數據:重大映射修改前建議備份重要數據。
- 測試環境驗證:先在測試環境驗證映射修改的效果。
通過合理規劃映射修改策略,可以在最小化影響的情況下實現索引結構的演進。
5.注意事項
- 提前規劃映射:生產環境中應預先定義好映射,避免依賴動態映射
- 避免映射爆炸:
- 設置
index.mapping.total_fields.limit
(默認 1000) - 使用
dynamic: false
或dynamic: strict
控制動態字段
- 設置
- 合理選擇數據類型:
- 需要全文搜索用
text
,需要精確匹配/聚合用keyword
- 數值類型選擇最合適的范圍(如能用
integer
就不用long
)
- 需要全文搜索用
- 元字段使用:
- 不要修改
_source
字段,它是文檔的原始 JSON - 使用
_routing
優化查詢性能
- 不要修改
- 映射更新限制:
- 已有字段的映射類型不能更改
- 只能添加新字段或修改某些參數(如增加字段的
fields
)
- 性能考慮:
- 避免過多的嵌套對象
- 對于不搜索的字段設置
"index": false
- 版本兼容性:
- Elasticsearch 7.x 及以后版本已移除映射類型概念
- 升級時要注意 API 變化
通過合理設計映射,可以顯著提高 Elasticsearch 的查詢性能和存儲效率。