订单当前状态和状态变更流水,数仓为什么要分开建模?
面试怎么回答
我会分别维护状态变更事件和当前状态投影。事件保留业务对象、发生时间、版本及前后状态,用来分析转移、耗时和历史路径;当前投影按可信顺序应用事件,服务当前待办查询。重复和乱序不能靠到达时间硬覆盖,撤销要作为明确变更保留。验收既核对当前状态,也检查事件覆盖和合法转移,不能从终态反推所有过程。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
报表能告诉你现在有多少待支付订单,却回答不了“昨天有多少订单从待支付转为已支付”。因为那列状态每天都被覆盖,变化过程已经不在了。保留最新值适合查询当前状态,但不能替代状态变更事实。
状态回答此刻是什么,事件回答如何变成这样;过程分析保留变更事实,当前查询维护可追溯投影。
当前表和事件表,回答的是不同时间问题
当前状态表可以一笔订单一行,用于查询待处理订单、当前退款状态或最新履约结果。它的重点是快速获得目前有效状态。
事件事实则一条有效变更一行,记录订单经历了什么。统计进入支付状态的次数、从创建到支付的耗时、某一步反复重试,都需要过程证据。
每日状态快照又是第三种产物,适合观察每天截面的存量。它能补充状态趋势,但若一天内发生多次变化,仅靠日快照仍可能看不到中间过程。
事件里最少需要哪些信息?
业务对象键、事件标识、业务发生时间和状态版本,是解释顺序的重要基础。前后状态、事件类型、来源系统与接收时间可以帮助判断更正和延迟。
业务时间与接收时间最好分开保留。晚到的旧状态不应该仅因为刚收到就覆盖新状态;但记录接收时间又有助于追踪链路迟到。
同一订单同一版本出现两种不同内容,应作为冲突处理,而不是简单按一个随机标识排序。稳定返回一条记录,并不等于业务冲突已经解决。

当前投影可以重建,过程事实不能靠猜
有完整且可靠的事件序列时,可以按规则计算当前状态,形成面向查询的投影。但事件源是否完整、初始状态从哪来,以及更正如何表达,都必须事先确认。
不要假设所有业务都严格向前走。支付可能撤销,审核可能退回,履约可能取消重建。状态编号较大也不一定更“新”,MAX(status_code)不能代表最后一次有效转移。
当前投影至少保留最后应用的版本或事件证据,便于判断一条重放消息已经处理过,还是需要更新。仅留状态值,会让恢复时无法解释它由哪个事件产生。
一个典型场景:终态正确,过程转化率仍然错
比如每天覆盖订单当前状态,月底想统计“支付后取消”的路径,直接筛选当前取消订单,再把它们都当作曾经支付。
结果包含大量从未支付就取消的订单。最初怀疑取消状态编码定义不清,回看业务协议发现编码没有问题,只是当前终态没有携带完整历史路径。
旧验收只和业务系统核对当天当前状态,当然全部一致;它从未验证过程分析所需的事件是否保留。修复应补充状态变更事实,并明确无法还原的历史期间,不能凭终态伪造过程。
以后新增漏斗或耗时指标,要先核对需要哪些转移证据,而不是等需求出现后再从最新快照里猜过去。
过程指标也有自己的计数粒度
一笔订单可能多次进入审核状态。统计审核次数与进入过审核的订单数,分别是事件粒度与订单去重粒度,不能混用。
计算状态停留时长时,要明确使用第一次进入、最后一次进入,还是各段累计。窗口内首个事件可能对应一个在窗口前已经开始的状态段,需要补前序基线。
尚未离开某状态的订单,其停留时长截至观察时点,不应当作零,也不能与已经完成的订单直接混算完成耗时均值。
测试可覆盖正常前进、重复事件、旧事件晚到、撤销回退和缺少初始状态。再核对当前投影与事件回放能否一致,发现缺口时应暴露未知,而不是强行补成合法路径。
分开状态与事件,并不是为了多建一张表,而是避免用一个最新字段承诺它根本无法回答的历史问题。