订阅更新

Flink SQL 声明 PRIMARY KEY 就会自动去重吗?NOT ENFORCED 是什么意思?

面试问 347 约 3 分钟读完

本文目录
  1. 面试怎么回答
  2. 先把数据库经验放到正确范围
  3. 同一个键再次出现,未必是重复
  4. “结果只有一行”不能证明没有重复计数
  5. 键要和真实粒度一致
  6. 一个典型场景:明细变少,金额却越来越大
  7. 面试官真正要听的是数据契约怎么兑现

面试怎么回答

Flink SQL的PRIMARY KEY NOT ENFORCED声明键唯一且非空,供优化和连接器使用,但Flink不会像拥有唯一索引的数据库那样替源数据查重。需要先区分追加事件与按键更新的变更流,再保证主键粒度正确。Sink按键覆盖只代表落地行为,不能消除上游重复聚合,也不保证迟到旧值不会覆盖新值;去重和版本规则要显式设计与验证。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

一张Flink表声明了PRIMARY KEY,源里重复发送两条记录,任务既没有报唯一键冲突,结果也不一定按预期去重。这个主键到底约束了谁?

NOT ENFORCED声明键唯一非空,不自动查重;要验证业务键,并区分状态更新与重复事件。

先把数据库经验放到正确范围

在具有唯一约束的数据库里,违反主键可能被存储层拒绝。但Flink通常不拥有外部源数据,声明主键不是给外部数据突然增加一个唯一索引。

以Flink 2.1为例,NOT ENFORCED表示不会按这种约束对输入输出做数据库式完整检查,使用者需要确保键的完整性。主键列还表达非空含义,并影响规划和连接器的假设。

所以它既不是自动修复工具,也不是可以随便写的注释。声明错误主键,可能让系统在错误前提下选择计划或解释更新。

同一个键再次出现,未必是重复

一条订单从待支付变成已支付,在变更流中可以是同一主键的合法更新。若把同键后续事件全部丢掉,就会丢失业务状态变化。

追加日志中的同一订单也可能存在多次合法动作,比如支付尝试与退款事件。它们的事件身份不等于订单身份。

应先确认源是追加流、更新流还是其他Changelog语义,再选表主键。最终状态表的一行身份,与事件流水的一行身份,往往需要不同的键。

这三个“按键处理”,不是同一项能力

“结果只有一行”不能证明没有重复计数

假设两条相同支付事件先进入求和聚合,金额已经加了两遍,随后把聚合结果按订单号覆盖写入目标。目标只有一行,但行里的金额仍然错误。

因此去重应发生在正确语义层:传输重复按稳定事件ID识别,状态更新按可信版本处理,合法业务重复保留。不能把这些情况都交给最终表覆盖。

即使直接写最新状态,外部Sink的覆盖逻辑也未必比较业务版本。较旧事件晚到时,可能覆盖较新状态。需要按连接器能力和业务规则处理乱序,不能默认主键就保证“最新”。

键要和真实粒度一致

如果订单号只在租户内唯一,主键必须考虑租户。若一行代表订单明细,只写订单号就把多条明细误当成同一对象。

声明前可以检查键空值、重复键样本和版本冲突,但仅一次离线扫描通过不代表未来输入永远符合。还要把约束检查纳入持续质量控制。

主键出现业务变更时,更要明确旧键删除与新键插入的表达。直接修改字符串字段但没有正确撤回旧记录,目标可能保留两份状态。

一个典型场景:明细变少,金额却越来越大

假设源事件重复投递,开发认为目标设置了主键会自动处理。看板发现部分金额翻倍,但目标表中查不到重复主键。

初步怀疑金额单位变了,排查才发现重复先进入累计计算,再把放大的结果更新到唯一目标行。旧验数只检查目标主键唯一,没有验证事件到聚合的贡献次数。

修正应在计算前按事件身份处理传输重复,并明确去重保留周期与历史回放边界。随后重算受影响结果,不能只删掉目标里的某几行再重启任务。

验收加入重复、乱序、更新、删除、空键与跨租户同号样本,分别确认内部计算和目标落地都符合定义。

面试官真正要听的是数据契约怎么兑现

可以先用一句话说明NOT ENFORCED不主动查重,再讲你如何保证输入身份、处理变更语义、核对目标行为。

还要说明不能随便补一个常量当主键。它可能让本来不同的行被视为同一行,甚至把数据覆盖掉。主键必须来自可证明的业务粒度,而不是为了让DDL通过语法检查。

刷题进度保存在当前浏览器