定义粒度是维度模型设计中最重要的决定。在创建任何表或定义任何列之前,数据架构师必须回答一个基本问题:事实表中的单行代表什么?粒度决定了可用于分析的详细程度。它规定了系统的维度性,并确定了物理存储需求。模式设计中的一个常见错误是在没有精确定义粒度的情况下进行,这会导致事实表包含不同详细程度的混合数据。这种模糊性会导致指标重复计算和查询逻辑混乱。分析的原子单元粒度是你的数据的原子单元。在零售交易的场景下,你面临两种常见粒度选择:订单头粒度: 每张单独的收据或订单一行。订单行粒度: 每张收据中每个扫描商品一行。如果你选择订单头粒度,你的事实表会记录总交易金额,但无法查看具体购买了哪些商品。如果你选择订单行粒度,你就能保留按单个商品、商品类别和品牌切分数据的能力。通常,分析系统应默认使用尽可能低的粒度,常被称为原子粒度。尽管这会增加行数,但原子粒度提供了最大的灵活性。聚合总是可以从原子数据汇总而来,但你无法向下查看从未存储过的细节。思考业务流程与结果表结构之间的关系。digraph G { rankdir=TB; node [shape=rect, style=filled, fontname="Helvetica", fontsize=10, color=white]; edge [color="#adb5bd", arrowsize=0.8]; subgraph cluster_0 { label=""; style=invis; Process [label="业务流程:\n零售结账", fillcolor="#4dabf7", fontcolor="white"]; } subgraph cluster_1 { label=""; style=invis; Grain1 [label="粒度选项 A:\n每张收据一行", fillcolor="#ff6b6b", fontcolor="white"]; Grain2 [label="粒度选项 B:\n每行商品一项", fillcolor="#51cf66", fontcolor="white"]; } subgraph cluster_2 { label=""; style=invis; Metric1 [label="指标:\n总金额\n税费\n门店ID", fillcolor="#fcc2d7", fontcolor="#495057"]; Metric2 [label="指标:\n数量\n单价\n商品ID\n门店ID", fillcolor="#b2f2bb", fontcolor="#495057"]; } Process -> Grain1 [label=" 订单头层级"]; Process -> Grain2 [label=" 明细层级"]; Grain1 -> Metric1; Grain2 -> Metric2; }不同的粒度决定了不同的描述性属性和指标。粒度选项 B 允许按商品进行分析,而粒度选项 A 不允许。事实表粒度的三个类别尽管具体的业务定义有所不同,但大多数粒度都属于三个技术类别之一。了解这些有助于为你的模式选择合适的模式。1. 交易粒度这是最精细的详细程度。当业务事件发生时,会精确地创建一个行。例如,网站点击、仓库移动或银行转账。优点: 最大的分析能力。你可以根据与事件相关的任何维度进行筛选和分组。缺点: 数据量大。对于长时间窗口的聚合,需要大量的存储和处理能力。2. 周期性快照粒度这种粒度代表了业务在特定、定期间隔的状态视图。与随机发生的交易不同,快照是确定性的。一个典型例子是每日库存余额或每月银行对账单。这里的一行不代表一个事件,而是代表一个时期结束时的状态。优点: 非常适合分析随时间变化的趋势和状态。由于数据已部分聚合,对于长期趋势,查询速度更快。缺点: 无法看到快照之间发生的活动。如果某商品在同一天内补货并售罄,每日快照可能会显示零变化,遗漏了库存周转速度。3. 累积快照粒度这适用于有明确开始和结束的工作流,例如订单处理或保险索赔。这里的粒度是“每个工作流实例一行”。当流程进入不同阶段时(例如,订单已下达 -> 已发货 -> 已送达),此行会更新。优点: 非常适合计算阶段之间的延迟时间和持续时间。缺点: 需要对数据仓库执行 UPDATE 操作,与仅追加模式相比,这在某些列式数据库中可能代价较高。混合粒度的问题当一个事实表试图同时存储多种粒度的数据时,模式设计会失败。考虑一个销售事实表。销售数据以订单行粒度到达。然而,业务也提供在月和门店级别定义的销售目标(预算)。如果你试图将每月预算目标存储在该月的每个订单行上,就会引入“扇出”问题。当分析师对 Budget 列求和时,他们会得到一个严重虚高的数字,因为每月目标会为每个售出商品重复。聚合公式会失效,因为指标的粒度与表的粒度不匹配:$$ \text{错误总计} = \sum_{i=1}^{n} (\text{交易}_i \times \text{月度预算}) $$为了解决这个问题,不同粒度的数据必须存放在不同的事实表中。你会设计一个 fact_sales 表(订单行粒度)和一个 fact_goals 表(月/门店粒度)。然后,这些表会在 BI 层或通过跨维度查询进行集成,而不是以违反粒度的方式进行物理连接。声明粒度在构建模式之前,你必须正式声明粒度。此声明作为表的约定。它通常包括识别业务流程以及与维度表的主要复合链接。例如,发货事实表的粒度声明可能如下所示:业务流程: 履约粒度定义: 发货清单上的每个行项目一行。维度: 日期、商品、客户、承运商、仓库。如果粒度定义变为“每个发货清单(卡车)一行”,则 Product 维度不再有效,因为一辆卡车可以运载多个商品。这说明粒度如何规定事实表中允许的外键。粒度选择的存储影响粒度选择与表大小存在直接线性关系。从每月快照变为每日快照,行数约增加 30 倍。从每日快照变为单个交易,行数可能增加数千或数百万倍,这取决于交易速度。以下图表呈现了一年内典型零售场景中粒度选择与行数之间的关系。{"layout": {"width": 600, "height": 400, "title": {"text": "粒度选择对行数的影响", "font": {"size": 16}}, "xaxis": {"title": "粒度类型", "showgrid": false}, "yaxis": {"title": "估计行数(对数刻度)", "type": "log", "showgrid": true, "gridcolor": "#dee2e6"}, "plot_bgcolor": "white", "paper_bgcolor": "white", "font": {"family": "Helvetica"}}, "data": [{"type": "bar", "x": ["月度汇总", "每日快照", "交易(原子)"], "y": [1200, 36500, 15000000], "marker": {"color": ["#748ffc", "#20c997", "#ff6b6b"]}, "text": ["1.2K", "36.5K", "15M"], "textposition": "auto"}]}更细的粒度会导致行数呈指数级增长。原子交易粒度(红色)提供最详细的数据,但需要最多的存储和计算资源。在现代云数据仓库中,存储相对便宜,计算可扩展。因此,建议几乎总是以交易(原子)粒度作为基础进行存储。如果查询性能因数据量而下降,你可以在原子数据之上构建周期性快照表作为派生聚合,但不应很少从聚合开始并丢弃细节。这种做法确保了模式能够满足未来可能需要详细历史分析的需求。