在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
以上文中如有不正之处欢送留言斧正
作者:京东批发 李振乾
内容起源:京东云开发者社区