文章目录
Elasticsearch 作为面向文档的搜索引擎,对嵌套数据的处理有多种方式,不同类型适用于不同的业务场景。
一、Object 类型:默认的嵌套对象处理方式
核心原理
Elasticsearch 中,JSON 文档的嵌套对象(如 {"user": {"name": "张三", "age": 25}}
)会被默认映射为 Object 类型
。其底层通过 字段扁平化 实现索引:将嵌套对象的字段展开为 父字段.子字段
的形式(如 user.name
、user.age
),存储为独立的字段。
典型场景
适用于 简单嵌套对象,且不需要对嵌套对象内部字段进行 关联查询 的场景。例如:
{
"article": {
"title": "ES 数据类型指南",
"author": {
"name": "李四",
"email": "lisi@example.com"
}
}
}
此时 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 类型:解决嵌套数组的关联查询
核心原理
Nested 类型是 Object 类型的扩展,专门用于处理 嵌套对象数组。它将数组中的每个对象 独立索引为一个“子文档”,保留对象内部字段的关联关系。查询时需使用 nested 查询
,确保只匹配同一嵌套对象内的字段。
典型场景
适用于 一对多关联 且需要对嵌套对象内部字段进行 组合查询 的场景。例如:
- 订单的多个商品(需查询“购买了苹果手机且数量>2的订单”);
- 用户的多个地址(需查询“城市为北京且邮编为100000的地址”)。
使用示例
映射定义:
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"}}
]
}
}
}
}
}
注意事项
- 性能与存储:每个嵌套对象独立索引,会增加索引体积(约为普通 Object 类型的 1.5~3 倍);
- 嵌套层级:支持多层嵌套(如
user.address.street
),但深度建议不超过 5 层(过深会影响查询性能); - 更新限制:修改嵌套对象的任意字段需重新索引整个父文档(类似关系型数据库的级联更新)。
三、Join 类型:跨文档的父子关联
核心原理
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 是单文档内的关联); - 分片限制:父文档和子文档必须同分片,若父文档分布不均可能导致分片数据倾斜;
- 适用场景:仅当父子文档需要 独立更新(如父文档修改不影响子文档)时使用,否则优先选择 Nested 类型。
四、Flattened 类型:动态嵌套对象的轻量化处理
核心原理
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": "张三" } // 直接使用展平后的字段名
}
}
注意事项
- 字段限制:展平后的字段数量默认最多 1000 个(可通过
max_flattened_fields
参数调整); - 查询能力:仅支持精确匹配(
term
)或前缀匹配(prefix
),无法进行范围查询(如age>20
); - 适用场景:优先用于 不需要复杂查询 的动态嵌套对象(如日志元数据),避免因动态映射导致字段爆炸。
五、类型对比与选择建议
类型 | 核心特点 | 适用场景 | 性能与限制 |
---|---|---|---|
Object | 扁平化存储,丢失嵌套对象关联 | 简单嵌套对象,无需关联查询 | 轻量,无法处理嵌套数组的关联查询 |
Nested | 独立索引嵌套对象,保留关联 | 一对多嵌套数组,需关联查询 | 索引体积大,更新成本高 |
Join | 跨文档父子关联 | 父子需独立更新,跨文档查询 | 查询性能差,分片限制严格 |
Flattened | 展平动态嵌套对象,简化索引 | 动态/未知结构的嵌套对象,轻查询 | 仅支持精确匹配,字段数量受限 |
选择建议:
- 优先使用
Nested 类型
处理嵌套数组的关联查询(如订单-商品); - 仅当父子需独立更新时使用
Join 类型
(如部门-员工); - 动态或未知结构的嵌套对象用
Flattened 类型
(如日志元数据); - 简单嵌套对象且无需关联查询时,使用默认的
Object 类型
。