写点什么

Elasticsearch 之 join 关联查询及使用场景 | 京东云技术团队

  • 2023-05-25
    北京
  • 本文字数:2741 字

    阅读完需:约 9 分钟

Elasticsearch之join关联查询及使用场景 | 京东云技术团队

在 Elasticsearch 这样的分布式系统中执行类似 SQL 的 join 连接是代价是比较大的,然而,Elasticsearch 却给我们提供了基于水平扩展的两种连接形式 。这句话摘自 Elasticsearch 官网,从“然而”来看,说明某些场景某些情况下我们还是可以使用的

一、join 总述

1、关系类比

在关系型数据库中,以 MySQL 为例,尤其 B 端类系统且数据量不是特别大的场景,我们经常用到 join 关键字对有关系的两张或者多张表进行关联查询。但是当数据量达到一定量级时,查询性能就是经常困扰的问题。由于 es 可以做到数亿量级的秒查(具体由分片数量决定),这时候把数据同步到 es 是我们可以使用解决方案之一。


那么不禁有疑问问了,由于业务场景的决定,之前必须关联查询的两张表还能做到进行关联吗?


答案是可以的,es 也提供了类似于关系型数据库的关联查询,但是它又与关系型数据的关联查询有明显的区别与限制。

2、使用场景

如果把关系数据库原有关联的两张表,同步到 es 后,通常情况下,我们业务开发中会有两种查询诉求的场景


场景 1


诉求:展示子表维度的明细数据(包含父表和子表中字段的条件)


方案:对于此种查询诉求,我们可以把原来关联的父子表打成父子表字段混合在一起的大宽表,既能满足查询条件,又有查询性能的保障,也是常用存储方案之一


场景 2


诉求:展示父表维度的明细数据(包含父表和子表中字段的条件)


方案:然而,对于此种查询诉求,需要通过子表的条件来查询出父表的明细结果,场景 1 的宽表存储方案是子表明细数据,而最终我们要的是父表明细数据,显然对于场景 1 的存储方案是不能满足的。如果非要使用场景 1 的存储方案,我们还要对宽表结果进行一次 groupby 或者 collapse 操作来得到父表结果。


这个时候我们就可以使用 es 提供的 join 功能来完成场景 2 的诉求查询,同时它也满足场景 1 的诉求查询

3、使用限制

由于 es 属于分布式文档型数据库,数据自然是存在于多个分片之上的。Join 字段自然不能像关系型数据库中的 join 使用。在 es 中为了保证良好的查询性能,最佳的实践是将数据模型设置为非规范化文档,通过字段冗余构造宽表,即存储在一个索引中。需要满足条件如下:


(1)父子文档(数据)必须存储在同一 index 中


(2)父子文档(数据)必须存储在同一个分片中,通过关联父文档 ID 关联


(3)一个 index 中只能包含一个 join 字段,但是可以有多个关系


(4)同一个 index 中,一个父关系可以对应多个子关系,一个子关系只对应一个父关系

4、性能问题

当然执行了 join 查询固然性能会受到一定程度的影响。对于带 has_child/has_parent 而言,其查询性能会随着指向唯一父文档的匹配子文档的数量增加而降低。本文开篇第一句摘自 es 官网描述,从 ES 官方的描述来看 join 关联查询对性能的损耗是比较大的。


不过,在笔者使用的过程中,在 5 个分片的前提下,且父表十万量级,子表数据量在千万量级的情况下,关联查询的耗时还是在 100ms 内完成的,对于 B 端许多场景还是可以接受的。


若有类似场景,建议我们在使用前,根据分片的多少和预估未来数据量的大小提前做好性能测试,防止以后数量达到一定程度时,性能有明显下降,那个时候再改存储方案得不偿失。

二、Mapping

1、举例说明

这里以优惠券活动与优惠券明细为例,在一个优惠券活动中可以发放几千万的优惠券,所以券活动与券明细是一对多的关系。


券活动表字段



券明细表字段


2、mapping 释义

join 类型的字段主要用来在同一个索引中构建父子关联关系。通过 relations 定义一组父子关系,每个关系都包含一个父级关系名称和一个或多个子级关系名称


activity_coupon_field 是一个关联字段,内部定义了一组 join 关系,该字段为自命名


type 指定关联关系是 join,固定写法


relations 定义父子关系,activity 父类型名称,coupon 子类型名称,名称均为自命名


{  "mappings": {    "properties": {      "activity_coupon_field": {        "type": "join",        "relations": {          "activity": "coupon"        }      },      "activity_id": {        "type": "keyword"      },      "activity_name": {        "type": "keyword"      },      "coupon_id": {        "type": "long"      },      "coupon_amount": {        "type": "long"      }    }  }}
复制代码

三、插入数据

1、插入父文档

在 put 父文档数据的时候,我们通常按照某种规则指定文档 ID,方便子文档数据变更时易于得到父文档 ID。比如这里我们用 activity_id 的值:activity_100 来作为父 id


PUT /coupon/_doc/activity_100 {  "activity_id": 100,  "activity_name": "年货节5元促销优惠券",  "activity_coupon_field": {    "name": "activity"  }}
复制代码

2、插入子文档

上边已经指定了父文档 ID,而子表中已经包含有 activity_id,所以很容易得到父文档 ID


put 子文档数据时候,必须指定父文档 ID,就是父文档中的_id,这样父子数据才建立了关联关系。与此同时还要指定 routing 字段为父文档 ID,这样保证了父子数据在同一分片上。


PUT /coupon/_doc/coupon_12345678?routing=activity_id_100 {  "coupon_id": 12345678,  "coupon_amount": "5",  "activity_id": 100,  "activity_coupon_field": {    "name": "coupon",    "parent": "activity_id_100" //父ID  }}
复制代码

四、关联查询

1、has_parent 查询(父查子)

根据父文档条件字段查询符合条件的子文档数据


例如:查询包含“年货节”活动字样,且已经被领取过的券


{  "query": {    "bool": {      "must": [{        "parent_type": "activity",        "has_parent": {          "query": {            "bool": {              "must": [{                "term": {                  "status": {                    "value": 1                  }                }              }, {                "wildcard": {                  "activity_name": {                    "wildcard": "*年货节*"                  }                }              }]            }          }        }      }]    }  }}
复制代码

2、has_child 查询(子查父)

根据子文档条件字段符合条件的父文档数据


例如:查询 coupon_id=12345678 在那个存在于哪个券活动中


{  "query": {    "bool": {      "must": [{        "has_child": {          "type": "coupon",          "query": {            "bool": {              "must": [{                "term": {                  "coupon_id": {                    "value": 12345678                  }                }              }]            }          }        }      }]    }  }}
复制代码


参考:Joining queries | Elasticsearch Guide [7.9] | Elastic


以上文中如有不正之处欢迎留言指正


作者:京东零售 李振乾

内容来源:京东云开发者社区

发布于: 刚刚阅读数: 3
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
Elasticsearch之join关联查询及使用场景 | 京东云技术团队_数据库_京东科技开发者_InfoQ写作社区