关于数据库:Elasticsearch之join关联查询及使用场景-京东云技术团队

10次阅读

共计 3049 个字符,预计需要花费 8 分钟才能阅读完成。

在 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、举例说明

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

券流动表字段

字段 阐明
activity\_id 流动 ID
activity\_name 流动名称

券明细表字段

字段 阐明
coupon\_id 券 ID
coupon\_amount 券面额
activity\_id 外键 - 流动 ID

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

以上文中如有不正之处欢送留言斧正

作者:京东批发 李振乾

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

正文完
 0