3. 数据建模与维度设计

在 Azure Synapse 中做数据仓库建模,核心不是“把表建出来”,而是先决定业务过程如何被度量、维度如何被组织、历史变化如何被保留。维度建模的经典选择是星型模型与雪花模型,二者在查询性能、ETL 复杂度和存储冗余上各有取舍。Synapse 作为 MPP 架构,表结构、分布方式和分区策略会直接影响 JOIN 与聚合效率,因此模型设计必须与物理实现同步考虑。

星型模型与雪花模型:先定事实,再定维度

星型模型以一张事实表为中心,维度表直接与事实表相连,维度表内部不再继续规范化。例如销售事实表 FactSales 包含 DateKey、ProductKey、StoreKey、Quantity、SalesAmount,而 DimProduct 直接保存产品名称、类别、品牌,不再拆出类别表。雪花模型则把维度进一步规范化,例如 DimProduct 只保留 CategoryKey,类别名称放在 DimCategory 中。星型模型 JOIN 更少、查询更直接,适合 Synapse 中面向分析的高并发场景;雪花模型减少冗余、便于维护层级维度,但会增加 JOIN 层数,在 MPP 中可能带来额外的数据移动。

在 Synapse 中,事实表通常选择 DISTRIBUTION = HASH(ProductKey) 或按日期分区,维度表若较小可使用 REPLICATE 分布,避免事实表与维度表 JOIN 时发生大规模 Shuffle。若维度表超过 2 GB,则不宜复制,应改用 HASH 分布并让事实表与维度表按同一键分布,实现共置 JOIN。

事实表设计:粒度、度量与分区

事实表设计的第一步是确定粒度。粒度越细,可回答的问题越多,但行数也越大。例如订单事实表可以按“订单行”粒度,也可以按“订单头”粒度。若业务需要分析单品销售,就必须使用订单行粒度。事实表通常包含两类列:外键列和度量列。外键列指向维度表,度量列是可加或半可加的数值,如 SalesAmount、Quantity、DiscountAmount。在 Synapse 中,事实表建议使用 CLUSTERED COLUMNSTORE INDEX,并按日期分区,例如 PARTITION (OrderDateKey RANGE RIGHT FOR VALUES ('2024-01-01','2024-02-01',...)),这样按月查询时只需扫描对应分区。

对于需要频繁更新的事实表,可考虑 HEAP 或 CLUSTERED INDEX,但列存索引在批量加载和聚合查询中优势更明显。加载时使用 COPY INTO 或 CTAS 将数据写入暂存表,再通过 INSERT ... SELECT 或 MERGE 合并到目标事实表,避免直接对列存表做大量单行更新。

缓慢变化维度:Type 1、Type 2 与 Synapse 实现

缓慢变化维度(SCD)解决维度属性随时间变化的问题。Type 1 直接覆盖旧值,不保留历史,适合修正错误数据;Type 2 保留历史,通过 StartDate、EndDate、IsCurrent 标记版本,适合需要追溯历史归属的场景,如客户所属区域变化。Type 3 增加“当前值”和“历史值”两列,适合只保留一次变化的简单场景。

在 Synapse 中实现 Type 2 的典型做法是:先准备增量维度数据,再与目标维度表比较。若业务键相同但属性变化,则关闭旧记录(设置 EndDate 和 IsCurrent=0),插入新记录(StartDate 为变更日期,EndDate 为 '9999-12-31',IsCurrent=1)。由于 Synapse 对列存表的更新代价较高,建议使用 CTAS 重建维度表或使用 MERGE 配合分区切换。以下示例展示 Type 2 的核心判断逻辑:

-- 关闭旧版本
UPDATE DimCustomer
SET EndDate = '2024-06-01', IsCurrent = 0
WHERE CustomerID = 'C001' AND IsCurrent = 1;

-- 插入新版本
INSERT INTO DimCustomer
(CustomerKey, CustomerID, CustomerName, Region, StartDate, EndDate, IsCurrent)
VALUES (NEXT VALUE FOR seq_customer_key, 'C001', '张三', '华东',
        '2024-06-01', '9999-12-31', 1);

实际工程中更推荐用 MERGE 或“先标记后插入”的批量方式,减少逐行更新。若维度表按 CustomerKey 哈希分布,需确保代理键生成稳定,避免重复。

维度表设计:代理键、层次与一致性

维度表应使用代理键(Surrogate Key)而非业务键作为主键,这样在 SCD Type 2 中同一业务实体可以有多个版本,事实表通过代理键关联到正确版本。日期维度是典型的一致性维度,建议预生成 DimDate,包含 DateKey、Date、Year、Quarter、Month、Week、IsWeekend 等字段,供所有事实表共用。产品维度可保留类别、子类别层次,若使用雪花模型则拆分类别表,若使用星型模型则直接冗余在 DimProduct 中。

在 Synapse 中,小维度表使用 REPLICATE 可显著加速 JOIN;大维度表使用 HASH 分布,并与事实表共置。维度表通常不需要分区,但若数据量很大,可按 LoadDate 分区。所有维度表都应包含 CreatedDate、ModifiedDate 等审计列,便于排查数据问题。

Azure Synapse 维度建模:星型模型与 SCD Type 2 关系图 事实表居中,维度表环绕;代理键关联,历史版本由 StartDate / EndDate / IsCurrent 控制 FactSales DateKey · ProductKey · StoreKey CustomerKey · Quantity SalesAmount · DiscountAmount HASH(ProductKey) · 按日期分区 CLUSTERED COLUMNSTORE INDEX DimDate DateKey · Date · Year Quarter · Month · Week IsWeekend · IsHoliday DimProduct ProductKey · ProductName Category · SubCategory Brand · UnitPrice DimStore StoreKey · StoreName City · Province · Region StoreType · OpenDate DimCustomer (SCD2) CustomerKey (代理键) CustomerID · Name · Region StartDate · EndDate IsCurrent DateKey ProductKey StoreKey CustomerKey SCD Type 2 版本示例 C001 · 华东 · 2023-01-01 ~ 2024-05-31 · 0 C001 · 华南 · 2024-06-01 ~ 9999-12-31 · 1 同一 CustomerID 对应两个 CustomerKey 事实表 维度表 SCD2 维度

在 Synapse 中落地时的关键检查点

第一,事实表与维度表的分布键要匹配。若事实表按 ProductKey 哈希分布,而 DimProduct 按 ProductKey 哈希分布,则 JOIN 可共置;若维度表使用 REPLICATE,则事实表任意分布均可。第二,SCD Type 2 的代理键生成要稳定,建议使用 IDENTITY 或序列,避免在并行加载时冲突。第三,日期维度必须提前生成并覆盖业务范围,否则事实表加载时会出现 DateKey 找不到对应维度行的问题。第四,所有维度表应设置 NOT NULL 和默认值,避免 NULL 导致 JOIN 结果丢失。第五,加载顺序应为“先维度、后事实”,确保事实表外键都能在维度表中找到对应代理键。

在 Synapse 中,星型模型通常比雪花模型更适合直接面向 Power BI 或 Synapse SQL 的聚合查询;若维度层级复杂且需要频繁维护,可对部分维度采用雪花模型,但应控制 JOIN 层数不超过 3 层,并尽量让大维度与事实表共置分布。

最后,模型不是一次建完就固定不变。随着业务问题变化,可能需要新增维度、调整粒度或增加 SCD 类型。建议在 Synapse 中保留一份模型元数据表,记录每张事实表和维度表的粒度、分布键、分区键、SCD 类型和加载策略,便于后续维护和团队协作。