加载中

Join 字段类型

join 数据类型是一个特殊字段,用于在同一索引的文档之间创建父/子关系。relations 部分定义了文档内可能的一组关系,每个关系由一个父名称和一个子名称组成。

警告

我们不建议使用多级关系来复制关系模型。每一级关系都会在查询时增加内存和计算方面的开销。为了获得更好的搜索性能,请改用非规范化(denormalize)你的数据。

可以按如下方式定义父/子关系

				PUT my-index-000001
					{
  "mappings": {
    "properties": {
      "my_id": {
        "type": "keyword"
      },
      "my_join_field": {
        "type": "join",
        "relations": {
          "question": "answer"
        }
      }
    }
  }
}
		
  1. 字段名称
  2. 定义了一个单一关系,其中 questionanswer 的父级。

要索引带有 join 的文档,必须在 source 中提供关系的名称以及文档的可选父级。例如,以下示例在 question 上下文中创建了两个 parent 文档

				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"
  }
}
		
  1. 此文档是一个 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"
}
		
  1. 父文档的更简单表示法只需使用关系名称。

索引子文档时,必须在 _source 中添加关系名称以及文档的父 ID。

警告

必须将父级的血缘关系(lineage)索引在同一个分片中,因此你必须始终使用其最高层父 ID 来路由子文档。

例如,以下示例展示了如何索引两个 child 文档

				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"
  }
}
		
  1. 路由值是必需的,因为父文档和子文档必须索引在同一个分片上
  2. answer 是此文档的 join 名称
  3. 此子文档的父 ID

不应像关系数据库中的 join 那样使用 join 字段。在 Elasticsearch 中,获得良好性能的关键是将数据非规范化(de-normalize)到文档中。每一个 join 字段、has_childhas_parent 查询都会对你的查询性能造成巨大的消耗。它还可能触发构建 全局序数(global ordinals)

join 字段有意义的唯一情况是,你的数据包含一对多关系,其中一个实体的数量远多于另一个实体。这种情况的一个例子是产品以及这些产品的报价(offer)。如果报价的数量远多于产品的数量,那么将产品建模为父文档、报价建模为子文档就是合理的。

  • 每个索引只允许有一个 join 字段映射。
  • 父文档和子文档必须索引在同一个分片上。这意味着在获取删除更新子文档时,需要提供相同的 routing 值。
  • 一个元素可以有多个子级,但只能有一个父级。
  • 可以向现有的 join 字段添加新关系。
  • 也可以向现有元素添加子级,但前提是该元素已经是一个父级。

父子关联创建一个字段来索引文档中关系的名称(my_parentmy_child 等)。

它还为每个父/子关系创建一个字段。此字段的名称是 join 字段的名称后跟 # 以及关系中父级的名称。例如,对于 my_parent → [my_child, another_child] 关系,join 字段会创建一个名为 my_join_field#my_parent 的附加字段。

如果文档是子级(my_childanother_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"
        ]
      }
    ]
  }
}
		
  1. 此文档属于 question join
  2. 此文档属于 question join
  3. 此文档属于 answer join
  4. 子文档链接的父 ID

有关更多信息,请参阅 has_childhas_parent 查询、children 聚合以及 内部命中(inner hits)

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" }
  ]
}
		
  1. 查询 parent id 字段(另请参见 has_parent 查询has_child 查询
  2. parent id 字段进行聚合(另请参见 children 聚合)
  3. 在脚本中访问 parent id 字段。

join 字段使用全局序数来加速 join。分片有任何更改后,都需要重新构建全局序数。分片中存储的父 ID 值越多,重新构建 join 字段的全局序数所需的时间就越长。

默认情况下,全局序数是急切构建的(eagerly built):如果索引发生了更改,join 字段的全局序数将作为刷新(refresh)的一部分进行重建。这会显著增加刷新的时间。然而,大多数时候这是一个正确的权衡,否则全局序数会在使用第一个父子关联查询或聚合时重建。这可能会为你的用户带来显著的延迟激增,并且通常情况会更糟,因为当发生大量写入时,可能会在单个刷新间隔内尝试多次重建 join 字段的全局序数。

join 字段使用不频繁且写入频繁时,禁用急切加载(eager loading)可能是明智的

				PUT my-index-000001
					{
  "mappings": {
    "properties": {
      "my_join_field": {
        "type": "join",
        "relations": {
           "question": "answer"
        },
        "eager_global_ordinals": false
      }
    }
  }
}
		

可以按如下方式检查每个父关系中全局序数使用的堆内存量

				
					# 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-000001
					{
  "mappings": {
    "properties": {
      "my_join_field": {
        "type": "join",
        "relations": {
          "question": ["answer", "comment"]
        }
      }
    }
  }
}
		
  1. questionanswercomment 的父级。
警告

我们不建议使用多级关系来复制关系模型。每一级关系都会在查询时增加内存和计算方面的开销。为了获得更好的搜索性能,请改用非规范化(denormalize)你的数据。

多级父/子关系

				PUT my-index-000001
					{
  "mappings": {
    "properties": {
      "my_join_field": {
        "type": "join",
        "relations": {
          "question": ["answer", "comment"],
          "answer": "vote"
        }
      }
    }
  }
}
		
  1. questionanswercomment 的父级
  2. answervote 的父级

上述映射表示以下树结构

   question
    /    \
   /      \
comment  answer
           |
           |
          vote
		

索引孙文档需要一个等于祖父级(血缘关系中的最高层父级)的 routing

				PUT my-index-000001/_doc/3?routing=1&refresh
					{
  "text": "This is a vote",
  "my_join_field": {
    "name": "vote",
    "parent": "2"
  }
}
		
  1. 此子文档必须与其祖父级和父级在同一个分片上
  2. 此文档的父 ID(必须指向一个 answer 文档)
© . This website operates independently and is not affiliated with or endorsed by Elasticsearch B.V. All brand names, logos, and trademarks are the property of their respective owners.