Elasticsearch 连接查询:嵌套查询与父子查询详解
在 Elasticsearch 中,处理关联数据(如一对多关系)时,传统的对象类型(object)会因内部扁平化存储导致查询异常。为此,ES 提供了两种专门的关联查询方式:嵌套查询(nested query) 和 父子查询(parent-child query),分别适用于不同的业务场景。
对象类型的局限性:为何需要连接查询?
对象类型(object)用于存储 JSON 对象数组,但 ES 会在内部将其扁平化处理,导致对象边界丢失,从而引发查询逻辑错误。
问题示例
假设有一篇包含多个事件的文档:
1 | { |
ES 内部会将其扁平化为:
1 | { |
此时查询 “title:hadoop 且 date 在 2022-10-01 至 2022-12-31 之间” 的事件,会错误匹配:
- 条件
title:hadoop匹配第二个事件(date:2022-08-08); - 条件
date范围匹配第一个事件(date:2022-10-10); - 扁平化存储导致 ES 认为 “存在同时满足两个条件的文档”,返回错误结果。
结论
对象类型仅适用于一对一关系,对于一对多关系,需使用嵌套查询或父子查询。
嵌套查询(Nested Query)
嵌套查询通过 nested 类型将对象数组存储为独立的 Lucene 文档,保留对象边界,确保多字段条件能正确关联到同一对象。
定义嵌套类型映射
需在映射中显式声明字段为 nested 类型:
1 | PUT conference |
插入嵌套文档
与普通对象数组格式一致,ES 会自动将 events 中的每个对象作为独立 Lucene 文档存储:
1 | PUT conference/default/1 |
执行嵌套查询
使用 nested 子句指定嵌套路径(path)和查询条件,确保条件仅匹配同一嵌套对象:
1 | GET conference/_search |
结果
该查询返回空结果(正确),因为 hadoop 事件的日期不在范围内。
嵌套查询的参数
path:嵌套对象在文档中的路径(如events)。query:作用于嵌套对象的查询条件(内部字段需用全路径,如events.title)。score_mode:嵌套对象的匹配得分如何影响父文档得分(默认avg,可选sum/max/min/none)。
优缺点与适用场景
| 优点 | 缺点 | 适用场景 |
|---|---|---|
| 保留对象边界,查询准确; 查询性能好(嵌套对象与父文档同分片); 支持聚合操作。 | 更新任一嵌套对象需重新索引整个文档; 嵌套层级过深可能影响性能。 | 嵌套对象不频繁更新的场景(如商品的规格参数、文章的评论)。 |
父子查询(Parent-Child Query)
父子查询通过 join 类型定义文档间的父子关系,父文档和子文档作为独立 ES 文档存储,更新子文档无需影响父文档,适合频繁更新的场景。
定义父子关系映射(6.x+)
6.x 后通过 join 类型替代旧版 _parent 字段,显式声明父子关系:
1 | PUT Q&A // 索引名 |
插入父文档与子文档
父文档:需指定
join字段的名称为父类型:1
2
3
4
5PUT Q&A/default/1?refresh // 父文档ID=1
{
"text": "什么是Elasticsearch?",
"my_join_field": { "name": "question" } // 声明为父类型
}子文档:需通过
routing参数确保与父文档同分片,并指定parent为父文档 ID:1
2
3
4
5
6
7
8PUT Q&A/default/101?routing=1&refresh // 子文档ID=101,routing=父文档ID
{
"text": "Elasticsearch是一款搜索引擎",
"my_join_field": {
"name": "answer", // 声明为子类型
"parent": "1" // 关联父文档ID=1
}
}注意:
routing确保父子文档存储在同一分片,否则无法查询关联关系。
常用父子查询
(1)has_child:通过子文档查询父文档
返回包含匹配子文档的父文档:
1 | GET Q&A/_search |
- 结果:返回父文档(ID=1),因为其包含匹配 “搜索引擎” 的子文档。
(2)has_parent:通过父文档查询子文档
返回关联匹配父文档的子文档:
1 | GET Q&A/_search |
- 结果:返回子文档(ID=101),因为其关联的父文档包含 “elasticsearch”。
(3)parent_id:通过父 ID 查询子文档
返回指定父文档的所有子文档:
1 | GET Q&A/_search |
- 结果:返回父文档 ID=1 的所有子文档(如 ID=101)。
优缺点与适用场景
| 优点 | 缺点 | 适用场景 |
|---|---|---|
| 子文档更新不影响父文档; 支持频繁更新的子文档。 | 查询性能低于嵌套查询(需跨文档匹配); 父子文档必须同分片(依赖 routing)。 |
子文档频繁更新的场景(如问答的回复、订单的物流记录)。 |
嵌套查询 vs 父子查询:如何选择?
| 维度 | 嵌套查询(Nested) | 父子查询(Parent-Child) |
|---|---|---|
| 存储方式 | 嵌套对象与父文档同 Lucene 文档 | 父子文档为独立 ES 文档 |
| 更新成本 | 更新子对象需重新索引整个文档 | 子文档更新不影响父文档 |
| 查询性能 | 高(同分片,无跨文档查找) | 中(需关联独立文档) |
| 适用场景 | 子对象不频繁更新(如商品属性) | 子对象频繁更新(如评论、日志) |
v1.3.10