本地英文版地址: ../en/parent-join.html
join数据类型是一种特殊的字段,它在同一个索引的文档中创建父-子关系。
relations部分定义了文档中一组可能的关系,每个关系都是一个父名称和一个子名称。
父-子关系可以定义如下:
PUT /my_index
{
"mappings": {
"properties": {
"my_id": {
"type": "keyword"
},
"my_join_field": {
"type": "join",
"relations": {
"question": "answer"
}
}
}
}
}
要使用 join 对文档进行索引,必须在source中提供关系的名称和文档的可选父文档。
例如,下面这个例子在question上下文中创建了两个 parent 文档:
PUT /my_index/_doc/1?refresh
{
"my_id": "1",
"text": "This is a question",
"my_join_field": {
"name": "question"
}
}
PUT /my_index/_doc/2?refresh
{
"my_id": "2",
"text": "This is another question",
"my_join_field": {
"name": "question"
}
}
在给父文档编制索引时,可以选择仅指定关系的名称作为快捷方式,而不是将其封装在普通对象符号中:
PUT my_index/_doc/1?refresh
{
"my_id": "1",
"text": "This is a question",
"my_join_field": "question"
}
PUT my_index/_doc/2?refresh
{
"my_id": "2",
"text": "This is another question",
"my_join_field": "question"
}
当为子文档编制索引时,必须在_source中添加关系的名称以及文档的父id。
需要在同一个分片中索引父文档的传承关系,因此必须始终使用更大的父id来路由子文档。
例如,下面这个例子显示了如何索引两个 child 文档:
PUT my_index/_doc/3?routing=1&refresh { "my_id": "3", "text": "This is an answer", "my_join_field": { "name": "answer", "parent": "1" } } PUT my_index/_doc/4?routing=1&refresh { "my_id": "4", "text": "This is another answer", "my_join_field": { "name": "answer", "parent": "1" } }
join 字段不应该像关系型数据库中的 JOIN 一样使用。
在Elasticsearch中,良好性能的关键是将数据反归一化到文档中。
每个 join 字段、has_child或has_parent查询都会显著提高查询性能。
join 字段有意义的唯一情况是数据包含一对多关系,其中一个实体的数量远远超过另一个实体。 这种情况的一个例子是产品及其优惠信息的用例。 在优惠信息明显多于产品数量的情况下,将产品建模为父文档,将优惠建模为子文档是有意义的。
父联结(parent join)创建一个字段来索引文档中关系的名称(my_parent, my_child,...)。
它还为每个父-子关系创建一个字段。
该字段的名称是join字段的名称,后跟#及关系中父字段的名称。
例如,对于my_parent → [my_child, another_child]关系,join字段会创建一个名为my_join_field#my_parent的附加字段。
如果该文档是一个子文档(my_child or another_child),则此字段包含文档链接到的父文档的_id,如果它是一个父文档(my_parent),则包含文档的_id。
当搜索一个包含join字段的索引时,搜索响应中总是返回这两个字段:
GET my_index/_search
{
"query": {
"match_all": {}
},
"sort": ["my_id"]
}
它将返回:
{
...,
"hits": {
"total" : {
"value": 4,
"relation": "eq"
},
"max_score": null,
"hits": [
{
"_index": "my_index",
"_type": "_doc",
"_id": "1",
"_score": null,
"_source": {
"my_id": "1",
"text": "This is a question",
"my_join_field": "question"
},
"sort": [
"1"
]
},
{
"_index": "my_index",
"_type": "_doc",
"_id": "2",
"_score": null,
"_source": {
"my_id": "2",
"text": "This is another question",
"my_join_field": "question"
},
"sort": [
"2"
]
},
{
"_index": "my_index",
"_type": "_doc",
"_id": "3",
"_score": null,
"_routing": "1",
"_source": {
"my_id": "3",
"text": "This is an answer",
"my_join_field": {
"name": "answer",
"parent": "1"
}
},
"sort": [
"3"
]
},
{
"_index": "my_index",
"_type": "_doc",
"_id": "4",
"_score": null,
"_routing": "1",
"_source": {
"my_id": "4",
"text": "This is another answer",
"my_join_field": {
"name": "answer",
"parent": "1"
}
},
"sort": [
"4"
]
}
]
}
}
更多信息,请参考has_child和has_parent查询,children聚合及内部命中(inner hits)。
可以在聚合和脚本中访问join字段的值,并且可以使用parent_id查询进行查询:
GET my_index/_search
{
"query": {
"parent_id": {
"type": "answer",
"id": "1"
}
},
"aggs": {
"parents": {
"terms": {
"field": "my_join_field#question",
"size": 10
}
}
},
"script_fields": {
"parent": {
"script": {
"source": "doc['my_join_field#question']"
}
}
}
}
|
查询 |
|
|
在 |
|
|
在脚本中访问父对象的id字段 |
字段使用join field uses 全局序号(global ordinals)来加速联结。
在对分片进行任何更改后,都需要重新构建全局序号。
一个分片中存储的父 id 值越多,为join字段重新构建全局序号所需的时间就越长。
默认情况下,会急切地构建全局序号:如果索引发生了变化,那么join字段的全局序号将作为 refresh 的一部分重新构建。
这可能会大大增加 refresh 的时间。
然而,大多数情况下这是正确的权衡,否则当使用第一个 父联结(parent-join) 查询或聚合时,将重新构建全局序号。
这可能会给你的用户带来显著的延迟峰值,通常情况下,当发生多次写入时,可能会在单个 refresh 间隔内尝试重建join字段的多个全局序号,这种情况会更糟。
当join字段很少使用但频繁写入时,禁用 快速加载(eager loading) 可能是有意义的:
PUT my_index
{
"mappings": {
"properties": {
"my_join_field": {
"type": "join",
"relations": {
"question": "answer"
},
"eager_global_ordinals": false
}
}
}
}
可以按如下方式检查每个父关系的全局序号使用的 heap 的大小:
# Per-index GET _stats/fielddata?human&fields=my_join_field#question # Per-node per-index GET _nodes/stats/indices/fielddata?human&fields=my_join_field#question
也可以为单个父对象定义多个子对象:
不建议使用多层关系来复制关系模型。 每一级关系都会在查询时增加内存和计算方面的开销。 如果你关心性能,应该对数据进行反归一化。
多层级的父-子关系:
PUT my_index
{
"mappings": {
"properties": {
"my_join_field": {
"type": "join",
"relations": {
"question": ["answer", "comment"],
"answer": "vote"
}
}
}
}
}
上面的映射表示下面的树状结构:
question
/ \
/ \
comment answer
|
|
vote
为孙子文档编制索引需要routing值等于曾祖父(曾祖父的传承):