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