ES中的数据关联

1、前言

对于solr来说是无法做两个collection之间的关联的,es是否可以做到类似于表的join关联那,这就是本篇需要研究的内容,

主要参考内容是官方文档。

先说下结论,如果不做特殊处理,es是无法完成类似与表Join的关联查询的。

2、ES如何做关联

官网里面有几种支持关联查询的办法:

2.1 应用程序做关联

  这个没有什么好说的,其实不算真正的关联,需要先查询一个索引,得到值构造出条件再去查询另外一个索引。

  缺点也很明显,就是需要查询多次。

2.2 冗余数据

   简单的来说就是将需要查询的数据保存在一起,举个例子如下:

    假设有用户索引和用户发布博客的索引,需要将用户和博客信息关联起来,对于用户来说,我们只需要取得用户的姓名信息即可,则可以这样做:
image.png

可以看下map信息如下:

实际在es内,已经将user下面的id和name进行了扁平化处理,可以通过如下的方式查询:


image.png
image.png

优点:查询速度非常快,缺点是存在数据的冗余。

2.3 嵌套对象

在ES中,对单个文档的增删改都是原子操作,有时候为了方便我们将实体和它相关的明细是放在一个文档中存储的。比如论坛发的帖子和它的回复信息。

其实和冗余对象有点类似,但是如果只是做查询会发现有问题,因为es扁平处理之后:

{
  "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 ]
}

tilte、body、tags被称为父文档或根文档。

这样数组内之间是没有顺序关系的,这就导致了后面的查询仍然可以查到数据,嵌套对象是为了解决这个问题的,先看下普通的对象:


image.png
{
  "query": {
    "bool": {
      "must": [
        { "match": { "comments.name": "Alice" }},
        { "match": { "comments.age":  28      }} ,
        { "match": { "comments.comment.keyword":  "Great article"     }}
      ]
    }
  }
}
image.png

嵌套对象,上面的例子是没有定义map的情况直接发送数据,comments被定义为object,失去了数组内的顺序关系,如果先定义了nested对象,则如下:

image.png
image.png
PUT my_index2
{
  "mappings": {
    "blogpost2": {
      "properties": {
        "comments": {
          "type": "nested",
          "properties": {
            "name":    { "type": "text"  },
            "comment": { "type": "text"  },
            "age":     { "type": "short"   },
            "stars":   { "type": "short"   },
            "date":    { "type": "date"    }
          }
        }
      }
    }
  }
}

再次发送相同的数据:

PUT /my_index2/blogpost2/10
{
  "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"
    }
  ]
}

再次发起查询:

image.png

为什么查不到,是因为nested对象有自己特定的语法如下:

image.png
{
  "query": {
    "nested": {
      "path": "comments",
       "score_mode": "max",
 
      "query": {
        "bool": {
          "must": [
            {
              "term": {
                "comments.age": 31
              }
            },
            {
              "term": {
                "comments.name": "alice"
             }
            }
          ]
        }
      }
    }
  }
}

score_mode:表示嵌套文档的最高得分纳入到根文档的计算之中。
嵌套模型的缺点如下:

当对嵌套文档做增加、修改或者删除时,整个文档都要重新被索引。嵌套文档越多,这带来的成本就越大。
查询结果返回的是整个文档,而不仅仅是匹配的嵌套文档。尽管目前有计划支持只返回根文档中最佳匹配的嵌套文档,但目前还不支持。

2.4 父子对象

父子对象是最类似与表join的对象,父子关系的对象分别位于不同的文档中,做到了很好的隔离。
有以下优点:
1)更新父文档或子文档时候,另一方不受影响。
2)创建和删除子文档,父文档不受到影响。
3)子文档可以作为独立的结果单独返回。

缺点是:
1)父文档和子文档必须存在同一个shard中。
2)貌似只能是同一个index的两个type(对于es6.x版本只能支持一个type,如何处理,目前还未看到)

原理:
Elasticsearch 维护了一个父文档和子文档的映射关系,得益于这个映射,父-子文档关联查询操作非常快。
但是这个映射也对父-子文档关系有个限制条件:父文档和其所有子文档,都必须要存储在同一个分片中。
父-子文档ID映射存储在 Doc Values 中。当映射完全在内存中时, Doc Values 提供对映射的快速处理能力,
另一方面当映射非常大时,可以通过溢出到磁盘提供足够的扩展能力
如何建立父子映射:
建立父-子文档映射关系时只需要指定某一个文档 type 是另一个文档 type 的父亲。 该关系可以在如下两个时间点设置:
1)创建索引时;
2)在子文档 type 创建之前更新父文档的 mapping。
举例来说,对于公司和员工之间存在着类似的关系,即可以将公司信息看成员工信息的父文档。

如下:

PUT /company
{
  "mappings": {
    "branch": {}, //公司type
    "employee": {
      "_parent": {
        "type": "branch" //employee 文档 是 branch 文档的子文档。
      }
    }
  }
}

父子文档的创建
1)对于父对象来说,它是不知道有多少个子对象的,所以按照一般的对象创建方法即可。
2)子对象创建方法:

PUT /company/employee/1?parent=london
{
  "name":  "Alice Smith",
  "dob":   "1970-10-24",
  "hobby": "hiking"
}

父文档 ID 有两个作用:创建了父文档和子文档之间的关系,并且保证了父文档和子文档都在同一个分片上。

这里面的父ID london 会作为路由的依据,这样子对象就会路由到父文档同一个shard上。
在执行单文档的请求时需要指定父文档的 ID,单文档请求包括:通过 GET 请求获取一个子文档;创建、更新或删除一个子文档。
而执行搜索请求时是不需要指定父文档的ID,这是因为搜索请求是向一个索引中的所有分片发起请求,而单文档的操作是只会向存储该文档的分片发送请求。
因此,如果操作单个子文档时不指定父文档的 ID,那么很有可能会把请求发送到错误的分片上。

父文档的 ID 应该在 bulk API 中指定

POST /company/employee/_bulk
{ "index": { "_id": 2, "parent": "london" }}
{ "name": "Mark Thomas", "dob": "1982-05-16", "hobby": "diving" }
{ "index": { "_id": 3, "parent": "liverpool" }}
{ "name": "Barry Smith", "dob": "1979-04-01", "hobby": "hiking" }
{ "index": { "_id": 4, "parent": "paris" }}
{ "name": "Adrien Grand", "dob": "1987-05-11", "hobby": "horses" }

通过子文档查询父文档
查询80后所在的公司信息:

GET /company/branch/_search
{
  "query": {
    "has_child": {
      "type": "employee",
      "query": {
        "range": {
          "dob": {
            "gte": "1980-01-01"
          }
        }
      }
    }
  }
}
image.png

查询至少两个员工的公司:

GET /company/branch/_search
{
  "query": {
    "has_child": {
      "type":         "employee",
      "min_children": 2,
      "query": {
        "match_all": {}
      }
    }
  }
}

通过父文档查询子文档

GET /company/employee/_search
{
  "query": {
    "has_parent": {
      "type": "branch",
      "query": {
        "match": {
          "country": "UK"
        }
      }
    }
  }
}
 
查询公司在UK的所有员工。

每个子文档都保存了父文档的ID。

image.png

当你考虑父子关系是否适合你现有关系模型时,请考虑下面这些建议

  • 尽量少地使用父子关系,仅在子文档远多于父文档时使用。
  • 避免在一个查询中使用多个父子联合语句。
  • 在 has_child 查询中使用 filter 上下文,或者设置 score_mode 为 none 来避免计算文档得分。
  • 保证父 IDs 尽量短,以便在 doc values 中更好地压缩,被临时载入时占用更少的内存。
  • 父子关联查询比普通的查询慢5-10倍。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容