在Elasticsearch中进行创建、删除和更新一个单个的文档的时候它是原子性的操作,所以在同一文档中存储紧密相关的实体是有道理的(百度翻译)。比如将一篇博客和它所有的评论存储在一起,可以通过一个comments数组实现:

PUT /my_index/blogpost/1
{
  "title": "Nest eggs",
  "body":  "Making your money work...",
  "tags":  [ "cash", "shares" ],
  "comments": [ 
    {
      "name":    "John Smith",
      "comment": "Great article",
      "age":     28,
      "stars":   4,
      "date":    "2014-09-01"
    },
    {
      "name":    "Alice White",
      "comment": "More like this please",
      "age":     31,
      "stars":   5,
      "date":    "2014-10-22"
    }
  ]
}



如果我们使用动态映射,commets字段将会被自动创建成一个object对象类型的字段

由于所有的内容都在一个文档中,所以在查询的时候不需要对bolg posts和commets进行联合查询,搜索性能也会更好

但是有时候会出现如下的一个问题:

GET /_search
{
  "query": {
    "bool": {
      "must": [
        { "match": { "comments.name": "Alice" }},
        { "match": { "comments.age":  28      }} 
      ]
    }
  }
}



我们想搜索评论里name中含有Alice、 age为28的评论者信息,但是搜索结果如下

es 嵌套查询seq_no_primary_term es bool嵌套_nested

可以看到JohnSimth和AliceWhite都被搜索了出来,因为John的age与搜索条件中的age为28匹配,Alice与搜索条件中name含有Alice匹配


造成这个的原因是在索引中结构化的JSON文档被扁平化成一个如下的键值对形式:

{
  "title":            [ eggs, nest ],
  "body":             [ making, money, work, your ],
  "tags":             [ cash, shares ],
  "comments.name":    [ alice, john, smith, white ],
  "comments.comment": [ article, great, like, more, please, this ],
  "comments.age":     [ 28, 31 ],
  "comments.stars":   [ 4, 5 ],
  "comments.date":    [ 2014-09-01, 2014-10-22 ]
}

Alice和31、John和2014-09-01之间的相关性被丢失了,对象类型的字段在存储一个单个的对象时是非常有用的,但是对存储一系列对象数组时就变得没用了。

这个问题可以使用nested对象来解决。将comments映射为一个nested对象类型而不是一个普通的object类型,每个nested对象被索引为隐藏的单独文档,如下所示:

{ //1
  "comments.name":    [ john, smith ],
  "comments.comment": [ article, great ],
  "comments.age":     [ 28 ],
  "comments.stars":   [ 4 ],
  "comments.date":    [ 2014-09-01 ]
}
{ //2
  "comments.name":    [ alice, white ],
  "comments.comment": [ like, more, please, this ],
  "comments.age":     [ 31 ],
  "comments.stars":   [ 5 ],
  "comments.date":    [ 2014-10-22 ]
}
{//3 
  "title":            [ eggs, nest ],
  "body":             [ making, money, work, your ],
  "tags":             [ cash, shares ]
}



1是第一个nested对象

2是第二个nested对象

3是根(父)文档


总结:

通过分别对每个嵌套对象进行索引,对象中字段之间的关系可以被维持。我们可以执行一个查询,只有match发生在同一个nested对象时它才会匹配。

不仅如此,由于nested对象被索引的方式,在查询的时候联合nested文档到根文档的速度是非常快的,几乎与查询单个的的文档一样快。

另外,nested文档是隐藏的,我们不能直接访问它。当更新、添加或者删除一个nested对象,必须重新索引整个文档,需要注意的是,发送一个搜索请求时返回的是整个文档而不是只返回nested对象。


翻译自官方文档:https://www.elastic.co/guide/en/elasticsearch/guide/current/nested-objects.html