连接字段类型
编辑连接字段类型编辑
join
数据类型是一种特殊字段,用于在同一索引的文档之间创建父子关系。relations
部分定义了文档中一组可能的关系,每个关系都是一个父名称和一个子名称。
我们不建议使用多级关系来复制关系模型。每一级关系都会在查询时增加内存和计算开销。为了获得更好的搜索性能,请对数据进行反规范化。
父子关系可以定义如下
response = client.indices.create( index: 'my-index-000001', body: { mappings: { properties: { my_id: { type: 'keyword' }, my_join_field: { type: 'join', relations: { question: 'answer' } } } } } ) puts response
PUT my-index-000001 { "mappings": { "properties": { "my_id": { "type": "keyword" }, "my_join_field": { "type": "join", "relations": { "question": "answer" } } } } }
要使用连接索引文档,必须在 source
中提供关系的名称和文档的可选父级。例如,以下示例在 question
上下文中创建两个 parent
文档
response = client.index( index: 'my-index-000001', id: 1, refresh: true, body: { my_id: '1', text: 'This is a question', my_join_field: { name: 'question' } } ) puts response response = client.index( index: 'my-index-000001', id: 2, refresh: true, body: { my_id: '2', text: 'This is another question', my_join_field: { name: 'question' } } ) puts response
PUT my-index-000001/_doc/1?refresh { "my_id": "1", "text": "This is a question", "my_join_field": { "name": "question" } } PUT my-index-000001/_doc/2?refresh { "my_id": "2", "text": "This is another question", "my_join_field": { "name": "question" } }
在索引父文档时,可以选择仅指定关系的名称作为快捷方式,而不是将其封装在正常的对象表示法中
PUT my-index-000001/_doc/1?refresh { "my_id": "1", "text": "This is a question", "my_join_field": "question" } PUT my-index-000001/_doc/2?refresh { "my_id": "2", "text": "This is another question", "my_join_field": "question" }
在索引子级时,必须在 _source
中添加关系的名称以及文档的父级 ID。
需要在同一个分片中索引父级的 lineage,因此您必须始终使用其更大的父级 ID 来路由子文档。
例如,以下示例显示了如何索引两个 child
文档
response = client.index( index: 'my-index-000001', id: 3, routing: 1, refresh: true, body: { my_id: '3', text: 'This is an answer', my_join_field: { name: 'answer', parent: '1' } } ) puts response response = client.index( index: 'my-index-000001', id: 4, routing: 1, refresh: true, body: { my_id: '4', text: 'This is another answer', my_join_field: { name: 'answer', parent: '1' } } ) puts response
PUT my-index-000001/_doc/3?routing=1&refresh { "my_id": "3", "text": "This is an answer", "my_join_field": { "name": "answer", "parent": "1" } } PUT my-index-000001/_doc/4?routing=1&refresh { "my_id": "4", "text": "This is another answer", "my_join_field": { "name": "answer", "parent": "1" } }
父子连接和性能编辑
连接字段不应像关系数据库中的连接那样使用。在 Elasticsearch 中,良好性能的关键是将数据反规范化为文档。每个连接字段、has_child
或 has_parent
查询都会增加查询性能的负担。它还可以触发构建 全局序号。
连接字段唯一有意义的情况是,如果您的数据包含一对多关系,其中一个实体的数量远远超过另一个实体。这种情况下,一个例子是产品和这些产品的报价用例。如果报价的数量远远超过产品的数量,那么将产品建模为父文档并将报价建模为子文档是有意义的。
父子连接限制编辑
使用父子连接进行搜索编辑
父子连接创建一个字段来索引文档中关系的名称(my_parent
、my_child
等)。
它还为每个父子关系创建一个字段。此字段的名称是 join
字段的名称,后跟 #
和关系中父级的名称。因此,例如,对于 my_parent
→ [my_child
, another_child
] 关系,join
字段会创建一个名为 my_join_field#my_parent
的附加字段。
如果文档是子级(my_child
或 another_child
),则此字段包含文档链接到的父级 _id
;如果文档是父级(my_parent
),则此字段包含文档的 _id
。
在搜索包含 join
字段的索引时,这两个字段始终在搜索响应中返回
GET my-index-000001/_search { "query": { "match_all": {} }, "sort": ["my_id"] }
将返回
{ ..., "hits": { "total": { "value": 4, "relation": "eq" }, "max_score": null, "hits": [ { "_index": "my-index-000001", "_id": "1", "_score": null, "_source": { "my_id": "1", "text": "This is a question", "my_join_field": "question" }, "sort": [ "1" ] }, { "_index": "my-index-000001", "_id": "2", "_score": null, "_source": { "my_id": "2", "text": "This is another question", "my_join_field": "question" }, "sort": [ "2" ] }, { "_index": "my-index-000001", "_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-000001", "_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
聚合和 内部命中。
join
字段的值可以在聚合和脚本中访问,并且可以使用 parent_id
查询 进行查询
GET my-index-000001/_search { "query": { "parent_id": { "type": "answer", "id": "1" } }, "aggs": { "parents": { "terms": { "field": "my_join_field#question", "size": 10 } } }, "runtime_mappings": { "parent": { "type": "long", "script": """ emit(Integer.parseInt(doc['my_join_field#question'].value)) """ } }, "fields": [ { "field": "parent" } ] }
查询 |
|
对 |
|
在脚本中访问 |
全局序号编辑
join
字段使用 全局序号 来加速连接。在对分片进行任何更改后,都需要重建全局序号。分片中存储的父级 ID 值越多,重建 join
字段的全局序号所需的时间就越长。
默认情况下,全局序号是急切构建的:如果索引已更改,则 join
字段的全局序号将在刷新过程中重建。这会增加刷新所需的时间。然而,大多数情况下,这是正确的权衡,否则全局序号会在第一次使用父子连接查询或聚合时重建。这可能会给您的用户带来明显的延迟峰值,而且通常情况下,当发生大量写入操作时,可能会在单个刷新间隔内尝试重建 join
字段的多个全局序号,这会使情况变得更糟。
当不经常使用 join
字段并且频繁发生写入操作时,禁用急切加载可能是有意义的
response = client.indices.create( index: 'my-index-000001', body: { mappings: { properties: { my_join_field: { type: 'join', relations: { question: 'answer' }, eager_global_ordinals: false } } } } ) puts response
PUT my-index-000001 { "mappings": { "properties": { "my_join_field": { "type": "join", "relations": { "question": "answer" }, "eager_global_ordinals": false } } } }
可以按父关系检查全局序号使用的堆内存量,如下所示
response = client.indices.stats( metric: 'fielddata', human: true, fields: 'my_join_field' ) puts response response = client.nodes.stats( metric: 'indices', index_metric: 'fielddata', human: true, fields: 'my_join_field' ) puts response
# 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
每个父级多个子级编辑
也可以为单个父级定义多个子级
response = client.indices.create( index: 'my-index-000001', body: { mappings: { properties: { my_join_field: { type: 'join', relations: { question: [ 'answer', 'comment' ] } } } } } ) puts response
多级父子连接编辑
我们不建议使用多级关系来复制关系模型。每一级关系都会在查询时增加内存和计算开销。为了获得更好的搜索性能,请对数据进行反规范化。
多级父子关系
response = client.indices.create( index: 'my-index-000001', body: { mappings: { properties: { my_join_field: { type: 'join', relations: { question: [ 'answer', 'comment' ], answer: 'vote' } } } } } ) puts response
PUT my-index-000001 { "mappings": { "properties": { "my_join_field": { "type": "join", "relations": { "question": ["answer", "comment"], "answer": "vote" } } } } }
上面的映射表示以下树
question / \ / \ comment answer | | vote
索引孙子文档需要一个等于祖父(lineage 中更大的父级)的 routing
值
response = client.index( index: 'my-index-000001', id: 3, routing: 1, refresh: true, body: { text: 'This is a vote', my_join_field: { name: 'vote', parent: '2' } } ) puts response