Elasticsearch 作为面向文档的搜索引擎,对嵌套数据的处理有多种方式,不同类型适用于不同的业务场景。
Elasticsearch 中,JSON 文档的嵌套对象(如 {"user": {"name": "张三", "age": 25}}
)会被默认映射为 Object 类型
。其底层通过 字段扁平化 实现索引:将嵌套对象的字段展开为 父字段.子字段
的形式(如 user.name
、user.age
),存储为独立的字段。
适用于 简单嵌套对象,且不需要对嵌套对象内部字段进行 关联查询 的场景。例如:
{
"article": {
"title": "ES 数据类型指南",
"author": {
"name": "李四",
"email": "[email protected]"
}
}
}
此时 author
是一个简单对象,若只需查询 author.name
或 author.email
的独立值(不关心是否属于同一作者),Object 类型足够。
当嵌套对象是 数组 时(如一个用户有多个地址),Object 类型会 丢失对象内部字段的关联。例如:
{
"user": "张三",
"addresses": [
{"city": "北京", "zip": "100000"},
{"city": "上海", "zip": "200000"}
]
}
Elasticsearch 会将 addresses.city
存储为 ["北京", "上海"]
,addresses.zip
存储为 ["100000", "200000"]
。若执行查询 city=北京 AND zip=200000
,会错误匹配到这条文档(因为字段被扁平化,不关心“北京”和“200000”是否属于同一个地址对象)。
Nested 类型是 Object 类型的扩展,专门用于处理 嵌套对象数组。它将数组中的每个对象 独立索引为一个“子文档”,保留对象内部字段的关联关系。查询时需使用 nested 查询
,确保只匹配同一嵌套对象内的字段。
适用于 一对多关联 且需要对嵌套对象内部字段进行 组合查询 的场景。例如:
映射定义:
PUT /my_index
{
"mappings": {
"properties": {
"user": {
"type": "nested", // 指定为 nested 类型
"properties": {
"name": {"type": "keyword"},
"address": {
"type": "nested", // 支持多层嵌套
"properties": {
"city": {"type": "keyword"},
"zip": {"type": "keyword"}
}
}
}
}
}
}
}
查询示例(匹配同一地址内的城市和邮编):
GET /my_index/_search
{
"query": {
"nested": {
"path": "user.address", // 指定嵌套路径
"query": {
"bool": {
"must": [
{"term": {"user.address.city": "北京"}},
{"term": {"user.address.zip": "100000"}}
]
}
}
}
}
}
user.address.street
),但深度建议不超过 5 层(过深会影响查询性能);Join 类型允许在 同一索引 中建立父子文档关系(如父文档是“用户”,子文档是“用户的订单”)。通过 _parent
字段关联父子文档,父文档和子文档需存储在 同一分片 上(通过父文档的 _id
哈希到分片)。
适用于 跨文档的一对多关联,且父子文档需要 独立更新 的场景。例如:
映射定义:
PUT /my_index
{
"mappings": {
"properties": {
"join_field": {
"type": "join",
"relations": {
"department": "employee" // 父类型: "department",子类型: "employee"
}
}
}
}
}
创建父文档(部门):
PUT /my_index/_doc/1
{
"name": "技术部",
"join_field": { "name": "department" } // 标记为父类型
}
创建子文档(员工):
PUT /my_index/_doc/2?routing=1 // 必须与父文档同分片(routing=父文档ID)
{
"name": "张三",
"join_field": {
"name": "employee",
"parent": "1" // 关联父文档ID
}
}
查询示例(查询技术部的所有员工):
GET /my_index/_search
{
"query": {
"has_parent": {
"parent_type": "department",
"query": { "term": { "name": "技术部" } }
}
}
}
JOIN
),性能低于 Nested 类型(Nested 是单文档内的关联);Flattened 类型用于将 复杂的嵌套 JSON 对象(如动态结构的元数据)展平为单个字段。所有嵌套的键会被合并为 点分隔的字符串
(如 {"a": {"b": "c"}}
会被展平为 a.b: c
),并索引为 keyword
类型(支持精确匹配)。
适用于 动态或未知结构的嵌套对象,例如:
tags
字段(可能包含任意层级的键值对);映射定义:
PUT /my_index
{
"mappings": {
"properties": {
"metadata": {
"type": "flattened" // 指定为 flattened 类型
}
}
}
}
文档示例:
PUT /my_index/_doc/1
{
"metadata": {
"user": {
"name": "张三",
"age": 25
},
"device": "iPhone 15"
}
}
此时 metadata
会被展平为 metadata.user.name: "张三"
、metadata.user.age: "25"
、metadata.device: "iPhone 15"
等字段。
查询示例(精确匹配展平后的路径):
GET /my_index/_search
{
"query": {
"term": { "metadata.user.name": "张三" } // 直接使用展平后的字段名
}
}
max_flattened_fields
参数调整);term
)或前缀匹配(prefix
),无法进行范围查询(如 age>20
);类型 | 核心特点 | 适用场景 | 性能与限制 |
---|---|---|---|
Object | 扁平化存储,丢失嵌套对象关联 | 简单嵌套对象,无需关联查询 | 轻量,无法处理嵌套数组的关联查询 |
Nested | 独立索引嵌套对象,保留关联 | 一对多嵌套数组,需关联查询 | 索引体积大,更新成本高 |
Join | 跨文档父子关联 | 父子需独立更新,跨文档查询 | 查询性能差,分片限制严格 |
Flattened | 展平动态嵌套对象,简化索引 | 动态/未知结构的嵌套对象,轻查询 | 仅支持精确匹配,字段数量受限 |
选择建议:
Nested 类型
处理嵌套数组的关联查询(如订单-商品);Join 类型
(如部门-员工);Flattened 类型
(如日志元数据);Object 类型
。