Parent-Child 建模 #
Parent-Child 用来表达"两个文档属于不同类型/生命周期,但又需要建立关联"的场景。相比 nested,它更适合父文档频繁变化 / 子文档数量较多 / 生命周期不同步的情况。
什么时候考虑 Parent-Child? #
典型场景:
- 主资源 + 活动记录:例如用户(父)+ 多条行为日志/评论(子)
- 订单 + 物流/状态变更记录:订单比较稳定,状态记录会持续追加
- 文档 + 标签/评分:标签或评分变化频率远高于主体文档
这类关系有几个共同特点:
- 父/子文档生命周期不同步(子可以频繁新增/删除,父相对稳定)
- 子文档数量可能很多,如果全部嵌入父文档会让父文档变得非常庞大
- 查询时既可能只查子文档,也可能需要"从父找子"或"从子找父"
Parent-Child 与 Nested 的对比 #
可以用下面的方式做一个快速选择:
更适合 Nested 的情况:
- 子元素数量有限,整体更新成本可接受
- 查询几乎总是"连带父文档一起看"
- 不需要单独对"子"做大规模搜索或独立生命周期管理
更适合 Parent-Child 的情况:
- 子元素数量较多,且经常新增/删除
- 子文档需要独立参与搜索与统计
- 父/子有不同的更新/存储策略
Nested 更像"文档内部的结构", Parent-Child 更像"两个文档集合之间的引用关系"。
建模要点(概念级) #
底层上,父文档与子文档都存储在同一个索引中,通过一个"连接字段"来描述父/子的关系:
- 父文档:在连接字段中声明自己是某个"父类型"
- 子文档:在连接字段中声明自己是某个"子类型",并携带父文档 ID
- 父/子关系只在这个索引内部生效,不能跨索引
实务上需要注意:
- 父/子文档通常需要落在同一个分片上,以保证查询效率与一致性
→ 路由策略(routing)必须以父文档为基准,写子文档时复用父文档的 routing。 - 一旦索引创建好,父/子结构(连接字段定义)很难修改,通常需要通过"新索引 + 重建数据"来演进。
具体的 mapping 与连接字段配置、查询语法,请以参考手册中的"关联查询"与字段类型文档为准,这里聚焦建模思路与取舍。
查询与聚合上的典型用法(进阶视角) #
有了父子关系之后,你可以表达例如:
- “找到包含满足某条件子文档的所有父文档”
- “找到拥有指定父文档的子文档子集”
在查询层面,通常会用到几类"关联查询":
has_child:从父的角度出发,筛选"拥有满足子查询条件的父文档"has_parent:从子的角度出发,筛选"其父文档满足某条件的子文档"parent_id:根据父 ID 定位子文档
配合聚合,可以:
- 按父文档分组统计子文档属性(例如每个用户的评论数、最近一条评论时间)
- 在子文档条件过滤后,对父文档层面的属性做分析(例如"最近 7 天内有投诉记录的用户,按地区分布")
这些需求如果用纯扁平索引或 nested 来表达,要么需要在应用层做二次 join,要么需要索引大量冗余数据。
性能与复杂度权衡 #
优势:
- 相比把所有信息塞进一个大文档,父/子分开可以:
- 降低单个文档的更新成本
- 对子文档的增删改更轻量
- 更适合大量子文档的场景(日志/评论/记录类)
成本与限制:
- 建模和查询语义更复杂,开发/调试成本更高
- 需要更谨慎地规划索引与路由策略,否则可能出现父/子落在不同分片的问题
- 某些查询与聚合在父/子混合场景下会显著更重,属于"昂贵查询"范畴
- 在部分配置下,如果禁止昂贵查询,对应的关联查询(如 has_child/has_parent)会被直接拒绝
经验建议:
- 如果 nested 能满足需求,优先尝试 nested(语义简单、查询更直观)
- 只有在 nested 明显不适合(子文档太多 / 生命周期差异很大)时,才考虑 Parent-Child
- 对使用了父子关系的索引:
- 控制索引规模与分片数,避免在"超大索引上跑海量父子关联查询"
- 为关键父子查询单独设计 API 与限流、超时策略,而不是开放为任意 DSL
小结 #
- Parent-Child 用来表达"两个文档属于不同类型/生命周期,但又需要建立关联"的场景
- 相比 nested,它更适合父文档频繁变化、子文档数量较多、生命周期不同步的情况
- 父/子文档通常需要落在同一个分片上,以保证查询效率与一致性
- 建模和查询语义更复杂,需要更谨慎地规划索引与路由策略
下一步可以继续阅读: