FlinkSQL Regular Join之 Left Join

一、Regular Join

        常规连接(Regular Join)是SQL中原生定义的Join方式,是最通用的一类连接操作。它的具体语法与标准SQL完全相同,通过关键字JOIN来连接两个表,后面用关键字ON来指明条件。

      Flink SQL的Regular Join也可以分为Inner Join 和 Outer Join,区别在于结果中是否包含不符合联结条件的行。Regular Join 包含以下几种(以 L 作为左流中的数据标识, R 作为右流中的数据标识):

  1. Inner Join:在流任务中,只有当两条流成功连接时才会输出结果,格式为 +[L, R]。
  2. Left Join:在流任务中,当左流数据到达时,无论是否与右流数据成功连接,均会输出结果。如果连接成功,输出格式为 +[L, R];如果连接失败,则输出 +[L, null]。若后续右流数据到达并与之前未连接的左流数据匹配成功,则会先发起一次回撤操作,输出 -[L, null],随后输出新的连接结果 +[L, R]。
  3. Right Join:与 Left Join 相似,但左右流的角色互换,即右流数据到达时,无论是否与左流数据成功连接,均会输出结果。
  4. Full Join:在流任务中,无论左流或右流的数据到达,都会尝试输出结果。对于未成功连接的情况,如果是右流数据,则输出 +[null, R];如果是左流数据,则输出 +[L, null]。若后续另一条流的数据到达并与之前未连接的数据匹配成功,则会先进行一次回撤操作 (对于左流数据到达,回撤 -[null, R];对于右流数据到达,回撤 -[L, null]),随后输出新的连接结果 +[L, R]。

二、案例

        接下来不玩虚的,直接上case,我司有一张评论表 `comment`,其中包含一级回复、二级回复数据,先要求计算每篇物料(动态、帖子)中状态正常的一级回复量、二级回复量,并将结果插入Redis中,表结构如下:

CREATE TABLE `comment` (
  `id` int(20) NOT NULL AUTO_INCREMENT,
  `entry_id` int NOT NULL COMMENT '被评论的实体id' ,一级评论对应的是物料ID,非一级评论对应的是其所评论的一级评论ID,
  `content` text NOT NULL,
  `create_time` timestamp NOT NULL ,
  `entry_type` int(11) NOT NULL COMMENT '被评论的实体类型',2 评论 8 帖子 73 博客 74 动态,
  `status` int(11) NOT NULL ,
  PRIMARY KEY (`id`),
);

我的初版代码如下:

CREATE TEMPORARY TABLE redis_statistic
(
  entity_id           string
  ,first_comment_cnt  string
  ,second_comment_cnt string
  ,PRIMARY KEY (entity_id) NOT ENFORCED
)
WITH (
  'connector' = 'redis'
  ,'host' = ''
  ,'port' = ''
  ,'password' = ''
  ,'mode' = 'HASHMAP'  
  ,'flattenHash' = 'true'
  ,'key-prefix' = 'comment_stat'
  ,'key-prefix-delimiter' = ':'
  ,'expiration' = '5184000000'
)
;


insert into redis_comment_statistic
select
   cast(t1.entry_type * 10000000000 + t1.entry_id as string) as entity_id
  ,cast(count(distinct t1.id) as string) as first_comment_cnt
  ,cast(count(distinct t2.id) as string) as second_comment_cnt
from (
  select
     entry_type
    ,entry_id
    ,id
  from `comment` 
  where `status` in (0,3)
    and entry_type <> 2
) t1
  left join (
  select
    entry_type
    ,entry_id
    ,id
  from `comment` 
  where `status` in (0,3)
    and entry_type = 2
) t2
    on t1.id = t2.entry_id
group by
  cast(t1.entry_type * 10000000000 + t1.entry_id as string)
having cast(t1.entry_type * 10000000000 + t1.entry_id as string) SIMILAR to '^74.*|^73.*|^80.*'
;

        由代码可知,我的数据通过左连接、聚合、筛选之后将结果以多值模式写入到 Redis 中的 HashMap中,然后通过下面命令查询:

HMGET comment_stat:74000xxxxx first_comment_cnt second_comment_cnt

        任务提交并且完成初始化之后,我试着在某物料下新增一条二级评论,但是Redis中数据并未发生更新,但是新增一条一级评论后Redis中的结果却发生了更新,明明同样的代码在MySQL中就没这种问题,问题到底出在哪里?

        因为生产上数据太多,并且对阿里云的flink还不熟悉,所以在线上探查问题还是不太方便,索性之前搭建过 Flink HA 集群,具体可看 Flink实战 - 搭建HA高可用集群,于是在其中fake数据查找问题,我先在MySQL中创建一张`dept`表并且插入相应的数据:

两版统计代码分别如下:

V1:错:

CREATE TABLE src_dept (
  id INT,
  entity_id int not null,
  entity_type int not null,
  content STRING,
  PRIMARY KEY (id) NOT ENFORCED
 ) WITH (
    'connector' = 'mysql-cdc',
    'hostname' = '',
    'port' = '',
    'username' = '',
    'password' = '',
    'database-name' = '',
    'table-name' = 'dept'
 );


select count(distinct t1.id) as first_comment_cnt
      ,count(distinct t2.id) as second_comment_cnt
from 
    (select * 
    from src_dept  
    where entity_type <> 2
    )  t1 
left join 
    (select * 
    from src_dept  
    where entity_type = 2
    ) t2
on t1.id = t2.entity_id
;

V2:对:

CREATE TABLE src_dept (
  id INT,
  entity_id int not null,
  entity_type int not null,
  content STRING,
  PRIMARY KEY (id) NOT ENFORCED
 ) WITH (
    'connector' = 'mysql-cdc',
    'hostname' = '',
    'port' = '',
    'username' = '',
    'password' = '',
    'database-name' = '',
    'table-name' = 'dept'
 );


select  count(distinct t1.id) as first_comment_cnt
       ,count(distinct t2.id) as second_comment_cnt
from  src_dept t1 
left join  src_dept t2
on t1.id = t2.entity_id
and t2.entity_type = 2
where t1.entity_type <> 2
;

V1、V2 初始化结果一样:

先在V1逻辑下向表中插入插入二级评论:

insert into dept values(7,3,2,'nihao');

结果没有变化

但是当我插入一级评论后,数据发生了更新

insert into dept values(8,224,74,'CIA');

接下来测试V2逻辑,分别向目标中插入1条一级二级评论,目标表中皆触发更新操作,结果如下:

insert into dept values(7,3,2,'nihao'),(8,224,74,'CIA');

三、总结

    尽管采用的代码基础相似,离线计算得出的数据结果并无差异,但在流式JOIN处理中却呈现出显著不同的表现。在我看来,关键在于V1版本实施LEFT JOIN操作时,尽管主表与从表源自相同的原始数据,它们各自在JOIN前经过了独立的筛选过程,且依据的筛选条件截然不同。这一过程等同于构建了两张结构与内容完全独立的临时表。值得注意的是,此场景中左表扮演驱动角色,意指左表数据的任何变动都将引发结果集的更新。然而,当仅向从表增添新的二级评论而主表未见新增时,由于驱动表(主表)未发生变化,最终的查询结果就不会反映出这次更新。

        转至V2版本,其差异性体现在处理逻辑顺序调整为先JOIN后FILTER,并且在此配置下,主表包含了全部数据记录。因此,不论是新增一级还是二级评论,由于初始就完成了与全量主表的JOIN操作,任何评论层级的新增都会立即在通过Redis存储的结果集中体现出来,触发相应的更新。简而言之,V2设计下的SQL执行流程及全量主表的使用共同确保了对各类新增评论的即时响应与数据同步。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值