订阅更新

事务事实表怎么设计?

面试问 061 约 2 分钟读完 2 次阅读

本文目录
  1. 面试怎么回答
  2. 第一步:先声明粒度
  3. 第二步:确定维度外键与退化维度
  4. 第三步:设计可汇总的度量
  5. 事务事实表和快照事实表有什么区别?
  6. 一个典型反例
  7. 生产中还要处理四件事

面试怎么回答

事务事实表记录不可再拆的业务事件,设计时第一步是声明粒度,例如每个订单明细、每次支付或每次退款一行;然后确定维度外键、退化维度和可加度量。不同业务过程不要因为共享订单号就混在同一粒度里,否则一对多 Join 会放大金额。生产中还要设计事件唯一键、迟到时间、重复幂等以及取消和冲正语义,保证明细可追溯、汇总不重复。

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

详细讲解

业务希望分析每一次下单、支付和退款:发生在什么时间、由谁发起、金额多少、来自哪个渠道。

这类需求关注的是“发生了什么”,不是某个时点的最终状态。

对应的核心模型,就是 事务事实表。

事务事实表的设计起点不是字段清单,而是:先定义一个不可再拆的业务事件,以及一行到底代表什么。


第一步:先声明粒度

比如订单场景,可以有不同粒度:

  • 一张订单一行
  • 一个订单明细一行
  • 一次支付流水一行
  • 一次退款申请一行

这些不是同一张事实表可以随意混放的概念。

如果声明“每个订单明细一行”,那么订单级优惠、运费和支付金额就不能不加说明地重复落到每个明细,否则汇总时会被放大。

第二步:确定维度外键与退化维度

事务发生时通常要关联:

  • 用户、商品、渠道、地区等维度键
  • 下单日期、支付日期等时间维度
  • 订单号、支付流水号等业务标识

订单号这类字段如果主要用于事实追踪,本身没有大量描述属性,可以作为退化维度保留在事实表中。

第三步:设计可汇总的度量

数量、原价金额、优惠金额、实付金额等通常可以按一定维度累加。

比率、客单价、转化率不能简单求和,应保存可加的分子和分母,在查询层重新计算。

事务事实表的粒度与字段分层


事务事实表和快照事实表有什么区别?

事务事实表记录一次事件,例如“支付了 100 元”。

周期快照事实表记录固定时点状态,例如“当天日终余额为 500 元”。

累计快照事实表记录一个流程对象从开始到结束的里程碑,例如订单创建、支付、发货和签收时间。

三者解决的问题不同。事务事实表适合追溯明细和汇总发生量,快照表更适合状态、存量和流程周期分析。


一个典型反例

一张“订单事实表”既放订单明细,又放多次支付和多次退款。

订单一对多明细,明细又一对多支付、退款,Join 后行数成倍放大。最后用 SUM(pay_amount) 汇总,支付金额被重复计算。

正确的做法通常是按业务过程拆开:订单明细事实、支付事务事实、退款事务事实分别保持自己的原子粒度,再通过一致的业务键和维度进行分析。

不要为了少建几张表,牺牲事实表粒度。


生产中还要处理四件事

  • 选择稳定的业务事件唯一键,保证重复同步时可以幂等
  • 明确取消、更正和冲正是更新原记录还是追加反向事件
  • 保存业务发生时间与数据入仓时间,识别迟到数据
  • 对金额、数量和行数建立平衡校验,防止漏数或重复

事务事实表并不意味着数据永远只 Insert。源系统的更正语义、CDC 方式和审计要求,会决定最终采用覆盖、追加版本还是冲正记录。


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