关于数据库: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、举例说明

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

券流动表字段

字段 阐明
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

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

作者:京东批发 李振乾

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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理