摘要
本文主要介绍了数据治理域中维度表设计的相关内容。首先阐述了维度在数据仓库建模中的重要性,它是事实 - 维度模型的核心组成部分,用于支持业务数据的多维分析。接着详细讲解了确定维度属性的步骤,包括从主维表和相关维表中选择或生成维度属性。然后列举了维度设计的经验总结,如维度字段要贴近业务语义、字段类型选择要合理等。还给出了确定维度属性的几点提示,如尽可能生成丰富的维度属性、区分数值型属性和事实等。
1. 维度基本概念
维度是描述“业务场景背景信息”的字段集合,它为业务指标(即“事实”)提供了分析角度。通俗来说:维度 = 看问题的角度。假设你分析一笔订单数据(事实),可以从以下角度进行分析:
| 角度 | 对应维度 |
|---|---|
| 这是谁下的单? | 用户维度(User Dimension) |
| 买的是什么商品? | 商品维度(Product Dimension) |
| 是在什么时候下的单? | 时间维度(Time Dimension) |
| 是哪个城市下的单? | 地理维度(Region/Location Dimension) |
| 是通过哪个渠道下单的? | 渠道维度(Channel Dimension) |
这些“角度”就是维度,订单金额、下单次数这些就是事实。
1.1. 🧱 维度设计的核心内容
| 设计要素 | 说明 |
|---|---|
| 业务驱动 | 维度要贴合业务理解,比如“会员等级”比“积分等级”更能被运营人员理解 |
| 枚举稳定性 | 维度值变化不应太频繁(如“省份”是稳定维度;“订单状态”若经常变化则应慎重处理) |
| 唯一主键(Surrogate Key) | 维度通常有自己的唯一主键,用于连接事实表(可以是业务主键或代理键) |
| 支持慢变维(SCD) | 对于“用户等级、商品价格”等会随时间变化的维度,需要考虑历史版本保留策略 |
| 可复用性(共享维度) | 同一维度(如用户维度)可以被多个事实表引用,形成总线架构模型 |
1.2. 🧠 常见维度类型
| 类型 | 说明 | 示例 |
|---|---|---|
| 时间维度 | 按日/月/季/年等时间粒度构建 | 日期、周、季度、节假日 |
| 地理维度 | 描述地区信息 | 国家、省、市、门店 |
| 用户维度 | 用户属性 | 年龄、性别、会员等级 |
| 商品维度 | 商品属性 | 类别、品牌、价格段 |
| 渠道维度 | 渠道来源 | App、H5、小程序、POS机 |
1.3. ⏳ 慢变维(SCD)是维度设计的重要点
维度信息是可能变化的,例如用户的职业、地址、会员等级等。我们需要设计好这些信息变化时的处理方式,称为 SCD(Slowly Changing Dimension)策略:
| 类型 | 说明 | 示例 |
|---|---|---|
| SCD Type 1 | 覆盖旧值,不保留历史 | 用户地址变更直接覆盖 |
| SCD Type 2 | 保留历史记录(加版本或有效期字段) | 记录每次变更 |
| SCD Type 3 | 同时保留当前值和上一个值 | 仅保存一个历史快照 |
1.4. 🎯 维度总结
| 维度作用 | 说明 |
|---|---|
| 作为事实的上下文 | 给度量值提供业务解释(金额是“谁”在“什么时候”花了) |
| 用于数据分析切片/钻取/分组 | 支持 OLAP 查询、透视分析 |
| 统一语义与共享 | 实现各个系统间数据一致性(统一的用户维度) |
| 支持数据治理 | 每个维度有明确的业务定义与数据血缘 |
2. 维度设计基本方法
维度设计的本质,是围绕业务事件构建“多角度分析结构”,最终形成可共享、可复用、可维护的数据结构体系。 维度设计是构建数据仓库和大数据平台的关键步骤之一,它决定了数据的可分析性、可理解性、可维护性。维度设计通常遵循 Kimball 的维度建模方法论,在实际应用中,特别适用于电商、金融、制造等领域的大数据分析场景。
2.1. ✅ 维度设计的基本方法(总览表)
| 维度设计方法 | 核心内容 | 说明/使用场景 |
|---|---|---|
| 1. 明确业务过程 | 先明确要分析的“业务事件” | 如:下单、支付、发货、退款、登录等 |
| 2. 识别维度角度 | 从业务分析需求出发,找出“分析角度” | 如:用户、时间、商品、渠道、门店 |
| 3. 设计维度表结构 | 列出维度主键、属性字段 | 例如:商品维度(商品ID、品牌、品类、价格段) |
| 4. 确定主键与唯一性 | 每个维度表需有唯一标识(一般是代理键) | 维度主键用于与事实表建立关联 |
| 5. 建立共享维度(Conformed Dimensions) | 构建可跨多个主题域共用的维度表 | 如“用户维度”被交易、营销、用户行为等多个主题复用 |
| 6. 慢变维策略(SCD) | 设计维度属性变更的处理机制 | 如:会员等级变化是否保留历史版本(SCD Type 2) |
| 7. 维度层级设计 | 支持分组、钻取的层级结构 | 如:时间维度(日 → 月 → 季度 → 年) |
| 8. 命名规范与注释 | 字段名业务语义明确,必须有描述 | 便于后续分析师、数据科学家理解使用 |
| 9. 标准化、枚举化 | 对枚举值(如性别、渠道)进行标准化 | 防止下游口径混乱 |
| 10. 维度可扩展性设计 | 预留扩展字段、考虑数据量增长 | 特别是用户、商品等高基数维度表 |
2.2. 🧱 常见维度建模策略
| 策略 | 类型 | 说明 |
|---|---|---|
| 维度键设计 | 自增代理键 vs 业务主键 | 推荐使用代理键,避免业务键变化带来关联混乱 |
| 慢变维处理 | SCD Type 1/2/3 | Type 2 常用于保留完整历史记录 |
| 退化维度(Degenerate Dimension) | 维度字段保留在事实表中,不单独建表 | 如订单号、交易流水号等 |
| 垃圾维度(Junk Dimension) | 将多个低基数属性合并为一个维度表 | 如支付方式、促销标记、是否首单等组合为“杂项维度” |
| 角色扮演维度(Role Playing Dimension) | 同一维度表在事实中扮演多个角色 | 如时间维度在订单中作为下单时间、支付时间、发货时间等 |
2.3. 📌 维度设计示例(用户维度)
下面以淘宝的商品维度为例对维度设计方法进行详细说明。
- 第一步:选择维度或新建维度。作为维度建模的核心,在企业级数据仓库中必须保证维度的唯一性。以淘宝商品维度为例,有且只允许有一个维度定义。
- 第二步:确定主维表。此处的主维表一般是ODS表,直接与业务系统同步。以淘宝商品维度为例,s_auction_auctions是与前台商品中心系统同步的商品表,此表即是主维表。
- 第三步:确定相关维表。数据仓库是业务源系统的数据整合,不同业务系统或者同一业务系统中的表之间存在关联性。根据对业务的梳理,确定哪些表和主维表存在关联关系,并选择其中的某些表用于生成维度属性。以淘宝商品维度为例,根据对业务逻辑的梳理,可以得到商品与类目、SPU、卖家、店铺等维度存在关联关系。
- 第四步:确定维度属性。本步骤主要包括两个阶段,其中第一个阶段是从主维表中选择维度属性或生成新的维度属性;第二个阶段是从相关维表中选择维度属性或生成新的维度属性。以淘宝商品维度为例,从主维表(s auction auctions)和类目、SPU、卖家、店铺等相关维表中选择维度属性或生成新的维度属性。
| 字段名 | 含义 | 类型 |
|---|---|---|
user_key | 用户维度代理键(主键) | bigint |
user_id | 用户业务主键 | string |
user_name | 用户名 | string |
gender | 性别(M/F) | string |
age | 年龄 | int |
member_level | 会员等级 | string |
register_date | 注册时间 | date |
start_date | 生效时间(SCD 2) | date |
end_date | 失效时间(SCD 2) | date |
is_current | 当前记录标记 | tinyint |
2.4. 📚 维度设计的经验总结
| 实践建议 | 说明 |
|---|---|
| ✅ 维度字段要贴近业务语义 | 便于业务人员理解使用 |
| ✅ 字段类型选择要合理 | 避免过长字符串、冗余字段 |
| ✅ 建立维度字典或码表 | 保证维度值的标准化(如地区编码、渠道名称) |
| ✅ 可视化维度数据关系 | 使用 ER 图或维度建模图形展示,清晰直观 |
| ❌ 不要将过于稀疏或不常用字段加入维度 | 会导致表膨胀,查询低效 |
确定维度属性的几点提示:
- 尽可能生成丰富的维度属性
比如淘宝商品维度有近百个维度属性,为下游的数据统计、分析探查提供了良好的基础。
- 尽可能多地给出包括一些富有意义的文字性描述
属性不应该是编码,而应该是真正的文字。在阿里巴巴维度建模中,一般是编码和文字同时存在,比如商品维度中的商品D和商品标题类目D和类目名称等。D一般用于不同表之间的关联,而名称一般用于报表标签。
- 区分数值型属性和事实
数值型字段是作为事实还是维度属性,可以参考字段的一般用途。如果通常用于查询约束条件或分组统计,则是作为维度属性;如果通常用于参与度量的计算,则是作为事实。比如商品价格,可以用于查询约束条件或统计价格区间的商品数量,此时是作为维度属性使用的;也可以用于统计某类目下商品的平均价格,此时是作为事实使用的。另外,如果数值型字段是离散值,则作为维度属性存在的可能性较大;如果数值型字段是连续值,则作为度量存在的可能性较大,但并不绝对,需要同时参考字段的具体用途。
- 尽量沉淀出通用的维度属性
有些维度属性获取需要进行比较复杂的逻辑处理,有些需要通过多表关联得到,或者通过单表的不同字段混合处理得到,或者通过对单表的某个字段进行解析得到。此时,需要将尽可能多的通用的维度属性进行沉淀。一方面,可以提高下游使用的方便性,减少复杂度;另一方面,可以避免下游使用解析时由于各自逻辑不同而导致口径不一致。例如,淘宝商品的property字段,使用key:value方式存储多个商品属性。商品品牌就存存储在此字段中,而商品品牌是重要的分组统计和查询约束的条件,所以需要将品牌解析出来,作为品牌属性存在。例如,商品是否在线,即在淘宝网站是否可以查看到此商品,是重要的查询约束的条件,但是无法直接获取,需要进行加工,加工逻辑是:商品状态为0和1且商品上架时间小于或等于当前时间,则是在线商品;否则是非在线商品。所以需要封装商品是否在线的逻辑作为一个单独的属性字段。
3. 维度层次结构
在维度建模中,维度层次结构(Dimension Hierarchy)是指维度中属性字段之间存在天然的上下级关系,这种结构是支持数据聚合(roll-up)、下钻(drill-down) 、切片(slice)、切块(dice)等 OLAP 操作的关键。
3.1. ✅ 什么是维度层次结构?
维度层次结构是指维度字段之间的逻辑组织顺序,代表“从粗到细”的多级数据结构。举个简单例子:
- 时间维度:年 → 季度 → 月 → 日
- 地理维度:国家 → 省份 → 城市 → 门店
- 商品维度:一级类目 → 二级类目 → 品牌 → 商品名
这些结构支持业务从“总体”看到“细节”,也便于报表进行自动汇总。
3.2. 📊 维度层次结构示意表
| 维度类型 | 层次结构 | 示例值 |
|---|---|---|
| 时间维度 | 年 → 季度 → 月 → 日 → 小时 | 2024 → Q2 → 6月 → 14日 → 16点 |
| 地理维度 | 国家 → 省份 → 城市 → 门店 | 中国 → 广东省 → 深圳市 → 罗湖门店 |
| 商品维度 | 一级类目 → 二级类目 → 品牌 → 商品ID | 数码 → 手机 → Apple → iPhone 15 |
| 组织维度 | 集团 → 部门 → 小组 → 员工 | 阿里 → 技术部 → 数仓组 → 张三 |
| 用户维度 | 用户等级 → 客户类型 → 用户ID | 金牌 → B端客户 → 123456 |
3.3. 🧠 为什么需要维度层次结构?
| 目的 | 说明 |
|---|---|
| ✅ 支持多层级聚合分析 | 如:按年统计销售额 → 按月看趋势 → 按天对比 |
| ✅ 支持下钻分析(Drill Down) | 从宏观到微观层层分析原因 |
| ✅ 提高数据模型组织性与可读性 | 层次结构清晰,更易维护 |
| ✅ 提升查询性能 | 多层级预聚合后查询更快(如构建多级 Cube) |
3.4. 🧱 维度层次设计要点
| 要点 | 说明 |
|---|---|
| 层次应有 明确的父子关系 | 如:1 个城市只能属于 1 个省 |
| 各层级字段应 可唯一标识当前层级 | 比如“月”应唯一对应“年” |
| 避免“歧义层级” | 例如一个商品属于多个品牌,这种结构就不适合作为标准层级维度 |
| 尽量使用代码 + 名称结构 | 如:province_code+ province_name,支持灵活展示与查询 |
| 支持“路径字段”形式 | 用于提前拼接好全路径,便于 Tree 查询(如 org_path = /阿里/技术部/数据组) |
3.5. 📌 维度层次建模举例
假设已有一个淘宝交易订单,创建事实表。现在统计2015年“双11”的下单GMV,得到一行记录;沿着层次向下钻取,添加行业,得到行业实例个数的记录数;继续沿着层次向下钻取,添加一级类目,得到一级类目实例个数的记录数。可以看到,通过向报表中添加连续的维度细节级别,实现在层次结构中进行钻取。
最高层级统计
行业层级统计
一级类目层次
商品维度建模举例
CREATE TABLE dim_product (
product_key BIGINT PRIMARY KEY,
product_id STRING,
product_name STRING,
brand_name STRING,
category_lvl1 STRING,
category_lvl2 STRING,
category_lvl3 STRING
);
建模后,你可以通过 SQL 聚合查询:
SELECT category_lvl1, category_lvl2, SUM(order_amount)
FROM fact_order
JOIN dim_product ON fact_order.product_key = dim_product.product_key
GROUP BY category_lvl1, category_lvl2;
3.6. 🧩 OLAP 引擎中的作用
| 功能 | 说明 |
|---|---|
| Roll-up(上卷) | 从日汇总到月、从城市汇总到省 |
| Drill-down(下钻) | 从年到季度到月逐层细化 |
| Slice(切片) | 选定某个城市或时间段 |
| Dice(切块) | 同时选择多维条件组合(如:6月 + 广州 + 手机类) |
4. 维度表的规范化与反规范化
在大数据建模中,维度表的规范化(Normalization)与反规范化(Denormalization) 是建模时的重要设计取舍问题,直接影响系统的性能、可维护性和使用便捷性。下面我们通过表格、示例和实际应用,来清晰对比这两种设计方式的异同与适用场景:
4.1. ✅ 规范化 vs 反规范化 对比表
| 比较项 | 规范化(Normalization) | 反规范化(Denormalization) |
|---|---|---|
| 定义 | 将数据拆分成多个逻辑实体,避免冗余 | 将相关数据合并到一张表,字段冗余,但查询方便 |
| 优点 | - 数据一致性高 - 节省存储空间 - 易维护 | - 查询性能高 - 结构直观简单 - 适合 OLAP 分析 |
| 缺点 | - 查询需多表 Join,性能较差 - 建模复杂 | - 数据冗余 - 更新维护成本高 - 容易产生数据不一致 |
| 使用场景 | - 事务系统(OLTP) - 数据源存储层 | - 数据仓库(OLAP) - 大数据数仓建模(如维度建模) |
| 典型应用 | - MySQL 中的 ER 模型建表 - 三级范式设计 | - Hive/Star Schema 中的维度建模 |
4.2. 🧱 示例:商品维度表
规范化结构
-- 一级分类维度
dim_category_lvl1 (cat1_id, cat1_name)
-- 二级分类维度
dim_category_lvl2 (cat2_id, cat2_name, cat1_id)
-- 商品维度
dim_product (
product_id,
product_name,
brand,
cat2_id -- 只关联二级分类
)
查询时需要多表 Join:
SELECT p.product_name, c1.cat1_name, c2.cat2_name
FROM dim_product p
JOIN dim_category_lvl2 c2 ON p.cat2_id = c2.cat2_id
JOIN dim_category_lvl1 c1 ON c2.cat1_id = c1.cat1_id;
反规范化结构(更常用于数据仓库)
dim_product (
product_id,
product_name,
brand,
cat1_name,
cat2_name
)
查询时无需 Join,直接使用:
SELECT cat1_name, COUNT(*)
FROM dim_product
GROUP BY cat1_name;
4.3. 🧩 维度建模中的通用建议
| 建议项 | 说明 |
|---|---|
| ✅ 大数据分析场景中,推荐使用反规范化 | 查询性能优先,避免复杂 Join |
| ✅ 反规范化后字段需标准命名、加注释 | 避免歧义,提升可读性 |
| ✅ 如有共享维度,保持唯一来源,统一口径 | 如“用户维度”可被多个主题域复用 |
| ❌ 不要过度反规范化造成“胖表”难维护 | 维度字段过多时可拆分二级维度或垃圾维度 |
| ✅ 若维度数据变更频繁,可保留“版本时间”字段(SCD) | 保证历史一致性和追溯能力 |
总结一句话:OLTP 系统倾向于维度表规范化,保证一致性;而 OLAP/大数据分析系统倾向于维度表反规范化,优先查询效率和使用方便。
5. 一致性维度和交叉探查
在大数据建模和数据仓库建设中,一致性维度(Conformed Dimensions) 和 交叉探查(Cross Fact Exploration) 是两个非常重要的概念,它们是实现多主题一致分析、共享维度体系和打通全局数据视角的核心手段。
5.1. ✅ 一致性维度(Conformed Dimension)
定义:一致性维度是指 在多个事实表(主题)之间共享的维度,这些维度在结构、内容、粒度、命名上完全一致,支持全局统一分析。📌 举例:“用户维度”被用于订单事实表、营销事件事实表、登录行为事实表等,它的定义、字段、取值在所有事实表中完全一致,这就是一致性维度。
5.2. 📊 一致性维度示意表
| 维度名称 | 应用主题 | 字段结构示例 |
|---|---|---|
| 用户维度 | 订单、营销、行为 | user_id, user_level, register_time |
| 时间维度 | 所有主题 | date_key, day, month, year, week |
| 商品维度 | 销售、库存、推荐 | product_id, category, brand |
5.3. ✅ 为什么要设计一致性维度?
| 原因 | 说明 |
|---|---|
| ✅ 支持多主题一致分析 | 可跨订单、营销、库存做整合分析 |
| ✅ 维度复用,避免重复建设 | 降低数据冗余和维护成本 |
| ✅ 统一数据口径 | 确保“用户等级”“品类名称”等含义一致 |
| ✅ 实现数据总线架构(Kimball) | 是数仓设计的核心架构理念之一 |
5.4. ✅ 交叉探查(Cross Fact Exploration)
定义:交叉探查是指:以某一个共享维度为主轴,在多个事实表之间进行联动分析或对比分析。📌 举例:
以“用户”为主维度,分析:
- 用户近 30 天登录次数(行为事实表)
- 用户最近下单金额(订单事实表)
- 用户最近点击广告次数(营销事实表)
👉 本质就是基于“一致性维度”做 跨主题事实数据对比分析。
5.5. 🧠 使用交叉探查的前提条件
| 条件 | 说明 |
|---|---|
| 共享维度一致(Conformed) | 否则无法 Join 分析 |
| 粒度兼容 | 例如:一个是“每日用户行为”,一个是“每次下单”,要注意汇总粒度 |
| 有业务需求 | 如:分析用户行为与转化效果之间的关系 |
5.6. 📊 交叉探查示意图
【维度表】dim_user
↓
┌────────────┬────────────┐
↓ ↓ ↓
fact_order fact_marketing fact_login
(下单) (广告点击) (登录)
在 BI 工具或数据开发中,可以用 dim_user.user_id 将多个事实表中的数据按用户聚合进行比较、分析、评分等。
5.7. ✅ 实际案例
🔍 电商行业案例:交叉分析下单用户和活跃用户行为
-- 统计最近一周有下单的用户及其登录频次
SELECT
u.user_id,
COUNT(DISTINCT o.order_id) AS order_cnt,
COUNT(DISTINCT l.login_date) AS login_days
FROM dim_user u
LEFT JOIN fact_order o ON u.user_id = o.user_id AND o.order_date >= '2025-06-01'
LEFT JOIN fact_login l ON u.user_id = l.user_id AND l.login_date >= '2025-06-01'
GROUP BY u.user_id;
总结一句话:一致性维度是构建统一数据视角的基础,交叉探查是实现多主题价值联动分析的手段,二者是打通数据孤岛的关键武器。
6. 维度整合
我们先来看数据仓库的定义:数据仓库是一个面向主题的、集成的、非易失的且随时间变化的数据集合,用来支持管理人员的决策。其中集成是数据仓库的四个特性中最重要的一个。
数据仓库的重要数据来源是大量的、分散的面向应用的操作型环境。不同的应用在设计过程中,可以自由决策,主要满足本应用的需求,很少会考虑和其他系统进行数据集成。应用之间的差异具体表现在如下几个方面:
- 应用在编码、命名习惯、度量单位等方面会存在很大的差异。比如不同应用对于用户的性别编码不同,有0和1、F和M等;不同应用的用户ID含义相同,但字段名称不同,有user、user id等;不同应用对于金额的度量单位不同,有元、分等。
- 应用出于性能和扩展性的考虑,或者随技术架构的演变,以及业务的发展,采用不同的物理实现。拆分至不同类型数据库中,部分数据采用关系型数据库存储(如Oracle、MySQL等),部分数据采用NoSQL数据库存储(如HBase、Tair等)。拆分成同一类型数据库中的多个物理表,比如对于淘宝商品,有商品主表和商品扩展表,商品主表存储商品基本信息,商品扩展表存储商品特殊信息,如不同产品线的定制化信息等;对于淘宝会员,有会员主表和会员扩展表,会员主表存储用户基本信息,会员扩展表存储用户扩展信息,如用户的各种标签信息等。
所以数据由面向应用的操作型环境进入数据仓库后,需要进行数据集成。将面向应用的数据转换为面向主题的数据仓库数据,本身就是一种集成。具体体现在如下几个方面:
- 命名规范的统一。表名、字段名等统一。
- 字段类型的统一。相同和相似字段的字段类型统一。
- 公共代码及代码值的统一。公共代码及标志性字段的数据类型、命名方式等统一。
- 业务含义相同的表的统一。主要依据高内聚、低耦合的理念,在物理实现中,将业务关系大、源系统影响差异小的表进行整合;
- 将业务关系小、源系统影响差异大的表进行分而置之。通常有如下几种集成方式:
-
- 采用主从表的设计方式,将两个表或多个表都有的字段放在主表中(主要基本信息),从属信息分别放在各自的从表中。对于主表中的主键,要么采用复合主键、源主键和系统或表区别标志;要么采用唯一主键、“源主键和系统或表区别标志”生成新的主键。通常建议采用复合主键的方式。
- 直接合并,共有信息和个性信息都放在同一个表中。如果表字段的重合度较低,则会出现大量空值,对于存储和易用性会有影响,需谨慎选择。
- 不合并,因为源表的表结构及主键等差异很大,无法合并,使用数据仓库里的多个表存放各自的数据。
维表的整合涉及的内容和上面介绍的几个方面相同,下面重点看表级别的整合,有两种表现形式。
- 第一种是垂直整合,即不同的来源表包含相同的数据集,只是存储的信息不同。比如淘宝会员在源系统中有多个表,如会员基础信息表、会员扩展信息表、淘宝会员等级信息表、天猫会员等级信息表,这些表都属于会员相关信息表,依据维度设计方法,尽量整合至会员维度模型中,丰富其维度属性。
- 第二种是水平整合,即不同的来源表包含不同的数据集,不同子集之间无交叉,也可以存在部分交叉。比如针对蚂蚁金服的数据仓库,其采集的会员数据有淘宝会员、1688会员、国际站会员、支付宝会员等,是否需要将所有的会员整合到一个会员表中呢?如果进行整合,首先需要考虑各个会员体系是否有交叉,如果存在交叉,则需要去重;如果不存在交叉,则需要考虑不同子集的自然键是否存在冲突,如果不冲突,则可以考虑将各子集的自然键作为整合后的表的自然键;另一种方式是设置超自然键,将来源表各子集的自然键加工成一个字段作为超自然键。在阿里巴巴,通常采用将来源表各子集的自然键作为联合主键的方式,并且在物理实现时将来源字段作为分区字段。
有整合就有拆分,到底是整合还是拆分,由多种因素决定。下面两节讨论维度的水平拆分和垂直拆分。
6.1. 水平拆分
维度通常可以按照类别或类型进行细分。比如淘系商品表,根据业务线或行业等可以对商品进行细分,如淘宝的商品、天猫的商品、1688的商品、飞猪旅行的商品、淘宝海外的商品、天猫国际的商品等。不同分类的商品,其维度属性可能相同,也可能不同。比如航旅的商品和普通的淘系商品,都属于商品,都有商品价格、标题、类型、上架时间、类目等维度属性,但是航旅的商品除了有这些公共属性外,还有酒店、景点、门票、旅行等自己独特的维度属性。如何设计维度?针对此问题,主要有两种解决方案:
- 方案1:是将维度的不同分类实例化为不同的维度,同时在主维度中保存公共属性;
- 方案2:是维护单一维度,包含所有可能的属性。
选择哪种方案?在数据模型设计过程中需要考虑的因素有很多,基本不可能满足各个特性指标的最优化。在设计过程中需要重点考虑以下三个原则。
扩展性:当源系统、业务逻辑变化时,能通过较少的成本快速扩展模型,保持核心模型的相对稳定性。软件工程中的高内聚、低耦合的思想是重要的指导方针之一效能:在性能和成本方面取得平衡。通过牺牲一定的存储成本,达到性能和逻辑的优化。
易用性:模型可理解性高、访问复杂度低。用户能够方便地从模型中找到对应的数据表,并能够方便地查询和分析。根据数据模型设计思想,在对维度进行水平拆分时,主要考虑如下两个依据。
- 第一个依据是维度的不同分类的属性差异情况。当维度属性随类型变化较大时,将所有可能的属性建立在一个表中是不切合实际的,也没有必要这样做,此时建议采用方案1。定义一个主维度用于存放公共属性;同时定义多个子维度,其中除了包含公共属性外,还包含各自的特殊属性。比如在阿里巴巴数据仓库维度体系中,依据此方法,构建了商品维度、航旅商品维度等。公共属性一般比较稳定,通过核心的商品维度,保证了核心维度的稳定性;通过扩展子维度的方式,保证了模型的扩展性。
- 第二个依据是业务的关联程度。两个相关性较低的业务,耦合在一起弊大于利,对模型的稳定性和易用性影响较大。比如在阿里巴巴数据仓库维度体系中,对淘系商品和1688商品构建两个维度。虽然淘系和1688在底层技术实现上是统一的,但属于不同的BU,业务各自发展;在数据仓库层面,淘系和1688属于不同的数据集市,一般不会相互调用,业务分析人员一般只针对本数据集市进行统计分析。如果设计成一个维度,由于不同BU业务各自发展,1688业务变更,此维度需要变更,淘宝业务变更亦然,稳定性很差;在易用性方面,会给数据使用方造成困扰。
6.2. 垂直拆分
在维度设计内容中,我们提到维度是维度建模的基础和灵魂,维度属性的丰富程度直接决定了数据仓库的能力。在进行维度设计时,依据维度设计的原则,尽可能丰富维度属性,同时进行反规范化处理。对于具体实现时可能存在的问题,一是在“水平拆分”中提到的,由于维度分类的不同而存在特殊的维度属性,可以通过水平拆分的方式解决此问题。
二是某些维度属性的来源表产出时间较早,而某些维度属性的来源表产出时间较晚;或者某些维度属性的热度高、使用频繁,而某些维度属性的热度低、较少使用;或者某些维度属性经常变化,而某些维度属性比较稳定。在“水平拆分”中提到的模型设计的三个原则同样适合解决此问题。
出于扩展性、产出时间、易用性等方面的考虑,设计主从维度。主维表存放稳定、产出时间早、热度高的属性;从维表存放变化较快、产出时间晚、热度低的属性。比如在阿里巴巴数据仓库中,设计了商品主维度和商品扩展维度。其中商品主维度在每日的1:30左右产出,而商品扩展维度由于有冗余的产出时间较晚的商品品牌和标签信息,在每日的3:00左右产出。另外,由于商品扩展维度有冗余的库存等变化较快的数据,对于主维度进行缓慢变化的处理较为重要。通过存储的冗余和计算成本的增加,实现了商品主模型的稳定和产出时间的提前,对于整个数据仓库的稳定和下游应用的产出都有较大意义。
6.3. 历史归档
在数据仓库中,可以借用前台数据库的归档策略,定期将历史数据归档至历史维表。在实践中,阿里巴巴数据仓库设计了商品维表和历史商品维表,每天将历史数据归档至历史商品维表。关于归档策略,有以下3种方式。
- 归档策略1:同前台归档策略,在数据仓库中实现前台归档算法,定期对历史数据进行归档。但存在一些问题,一是前台归档策略复杂,实现成本较高;二是前台归档策略可能会经常变化,导致数据仓库归档算法也要随之变化,维护和沟通成本较高。此方式适用于前台归档策略逻辑较为简单,且变更不频繁的情况。
- 归档策略2:同前台归档策略,但采用数据库变更日志的方式。对于如此庞大的数据量,阿里巴巴采用的数据抽取策略一般是通过数据库binlog日志解析获取每日增量,通过增量merge全量的方式获取最新的全量数据。可以使用增量日志的删除标志,作为前台数据归档的标志。通过此标志对数据仓库的数据进行归档。此方式不需要关注前台归档策略,简单易行。但对前台应用的要求是数据库的物理删除只有在归档时才执行,应用中的删除只是逻辑删除。
- 归档策略3:数据仓库自定义归档策略。可以将归档算法用简单,直接的方式实现,但原则是尽量比前台应用晚归档、少归档。避免出现数据仓库中已经归档的数据再次更新的情况。
如果技术条件允许,能够解析数据库binlog日志,建议使用归档策略2,规避前台归档算法。具体可以根据自身数据仓库的实际情况进行选择。
7. 维度变化
7.1. 缓慢变化维
数据仓库的重要特点之一是反映历史变化,所以如何处理维度的变化是维度设计的重要工作之一。缓慢变化维的提出是因为在现实世界中,维度的属性并不是静态的,它会随着时间的流逝发生缓慢的变化。
与数据增长较为快速的事实表相比,维度变化相对缓慢。在一些情况下,保留历史数据没有什么分析价值;而在另一些情况下,保留历史数据将会起到至关重要的作用。在Kimball的理论中,有三种处理缓慢变化维的方式。
- 第一种处理方式:重写维度值。采用此种方式,不保留历史数据始终取最新数据。比如,商品所属的类目于2015年11月16日由类目1变成类目2,采用第一种处理方式,变化前后的数据记录分别如表10.4和表10.5所示。
- 第二种处理方式:插入新的维度行。采用此种方式,保留历史数据,维度值变化前的事实和过去的维度值关联,维度值变化后的事实和当前的维度值关联。同上面的例子,采用第二种处理方式,变化前的数据记录同表10.4,变化后的数据记录如表10.6所示。
第三种处理方式:添加维度列。采用第二种处理方式不能将变化前后记录的事实归一为变化前的维度或者归一为变化后的维度。比如根据业务需求,需要将11月份的交易金额全部统计到类目2上,采用第二种处理方式无法实现。针对此问题,采用第三种处理方式,保留历史数据,可以使用任何一个属性列。同上面的例子,采用第三种处理方式,变化前后的数据记录分别如表10.7和表10.8所示。通过变化后的商品表和订单表关联,可以根据不同的业务需求,将11月份的交易金额全部统计到类目2或类目1上。
对于选择哪种方式处理缓慢变化维,并没有一个完全正确的答案,可以根据业务需求来进行选择。比如根据商品所属的类目统计淘宝2015年11月的成交额,商品所属的类目于2015年11月16日由类目1变成类目2,假设业务需求方不关心历史数据,将所有的成交额都统计到最新的类目2上,则不需要保存历史数据:假设类目1属于某个业务部门,类目2属于另一个业务部门,不同业务部门需要统计各自的业绩,则需要保留历史数据。
7.2. 快照维表
在“维度的基本概念”中,介绍了自然键和代理键的定义,在Kimball的维度建模中,必须使用代理键作为每个维表的主键,用于处理缓慢变化维。但在阿里巴巴数据仓库建设的实践过程中,虽然使用的是Kimball的维度建模理论,但实际并未使用代理键。那么为什么不使用代理键?如何处理缓慢变化维?
首先看“为什么不使用代理键”这个问题。第一个原因是,阿里巴巴数据量庞大,使用的是阿里巴巴自助知识产权的分布式计算平台MaxCompute。.对于分布式计算系统,不存在事务的概念,对于每个表的记录生成稳定的全局唯一的代理键难度很大,此处稳定指某条记录每次生成的代理键都相同。第二个原因是,使用代理键会大大增加ETL的复杂性,对ETL任务的开发和维护成本很高。
接下来讨论不使用代理键如何处理缓慢变化维的问题。在阿里巴巴数据仓库实践中,处理缓慢变化维的方法是采用快照方式。数据仓库的计算周期一般是每天一次,基于此周期,处理维度变化的方式就是每天保留一份全量快照数据。比如商品维度,每天保留一份全量商品快照数据。任意一天的事实均可以获取到当天的商品信息,也可以获取到最新的商品信息,通过限定日期,采用自然键进行关联即可。此方法既有优点,也有弊端。
优点主要有以下两点:
- 简单而有效,开发和维护成本低。使用方便,理解性好。数据使用方只需要限定日期,即可获取到当天的快照数据。任意一天的事实快照和维度快照通过维度的自然键进行关联即可。
- 弊端主要体现在存储的极大浪费上。比如某维度,每天的变化量占总体数据量的比例很低,在极端情况下,每天无变化,使得存储浪费很严重。此方法主要就是实现了牺牲存储获取ETL效率的优化和逻辑上的简化。但是一定要杜绝过度使用这种方法,而且必须要有对应的数据生命周期制度,清除无用的历史数据。
综合来看,由于现在存储成本远低于CPU、内存等的成本,此方法弊大于利。那么是否有方法既可以实现上面的优点,同时又可以很好地降低存储呢?答案是肯定的,那就是阿里巴巴的极限存储。
7.3. 极限存储
首先来看历史拉链存储。历史拉链存储是指利用维度模型中缓慢变化维的第二种处理方式。这种处理方式是通过新增两个时间戳字段(start dt和end dt),将所有以天为粒度的变更数据都记录下来。通常分区字段也是时间戳字段。
例如,2016年1月1日,卖家A在淘宝网发布了B、C两个商品,前端商品表将生成两条记录t1、t2;1月2日,卖家A将B商品下架了,同时又发布了商品D,前端商品表将更新记录t1,又新生成记录3;采用全量存储方式,在1月1日这个分区中存储t1和2两条记录;在1月2日这个分区中存储更新后的t1以及t2、t3记录。数据存储记录如表10.9所示。
如果采用历史拉链存储,数据存储记录如表10.10所示。对于不变的数据,不再重复存储。
但是这种存储方式对于下游使用方存在一定的理解障碍,特别是ODS数据面向的下游用户包括数据分析师、前端开发人员等,他们不怎么理解维度模型的概念,因此会存在较高的解释成本。另外,这种存储方式用start dt和end dt做分区,随着时间的推移,分区数量会极度膨胀,而现行的数据库系统都有分区数量限制。为了解决上述两个问题,阿里巴巴提出采用极限存储的方式来处理。
- 透明化: 底层的数据还是历史拉链存储,但是上层做一个视图操作或者在Hive里做一个hook,通过分析语句的语法树,把对极限存储前的表的查询转换成对极限存储表的查询。对于下游用户来说,极限存储表和全量存储方式是一样的:
Select * from A where ds =20160101
等价于
Select * from A EXST where start dt <=20160101 and end dt>20160101;
2. 分月做历史拉链表
假设用start dt和end dt做分区,并且不做限制,那么可以计算出一年历史拉链表最多可能产生的分区数是:365×364/2=66430个。如果在每个月月初重新开始做历史拉链表,目录结构如下:
再计算一年最多可能产生的分区数是:12×(1+(30+29)/2)=5232个。
采用极限存储的处理方式,极大地压缩了全量存储的成本,又可以达到对下游用户透明的效果,是一种比较理想的存储方式。但是其本身也有一定的局限性,首先,其产出效率很低,大部分极限存储通常需要t-2,
其次,对于变化频率高的数据并不能达到节约成本的效果。因此,在实际生产中,做极限存储需要进行一些额外的处理。
- 在做极限存储前有一个全量存储表,全量存储表仅保留最近一段时间的全量分区数据,历史数据通过映射的方式关联到极限存储表。即用户只访问全量存储表,所以对用户来说极限存储是不可见的。
- 对于部分变化频率频繁的字段需要过滤。例如,用户表中存在用户积分字段,这种字段的值每天都在发生变化,如果不过滤的话,极限存储就相当于每个分区存储一份全量数据,起不到节约存储成本的效果。
7.4. 微型维度
采用极限存储,需要避免维度的过度增长。比如对于商品维表,每天20多亿条数据,如果在设计商品维度时,将值变化频繁的属性加入到商品维度中,极限情况是每天所有商品数据都发生变化,此时,极限存储没有意义;反之,每天所有商品数据都不发生变化,此时,只需要存储一天的数据即可。
通过将一些属性从维表中移出,放置到全新的维表中,可以解决维度的过度增长导致极限存储效果大打折扣的问题。其中一种解决方法就是上一节提到的垂直拆分,保持主维度的稳定性;另一种解决方式是采用微型维度。
微型维度的创建是通过将一部分不稳定的属性从主维度中移出,并将它们放置到拥有自己代理键的新表中来实现的。这些属性相互之间没有直接关联,不存在自然键。通过为每个组合创建新行的一次性过程来加载数据。比如淘宝用户维度,用户的注册日期、年龄、性别、身份信息等基本不会发生变化,但用户VIP等级、用户信用评价等级会随着用户的行为不断发生变化。其中VIP等级共有8个值,即-16;用户信用评价等级共有18个值。假设基于VIP等级和用户信用评价等级构建微型维度,则在此微型维度中共有8×18个组合,即144条记录,代理键可能是1144。
但在阿里巴巴数据仓库实践中,并未使用此技术,主要有以下几点原因:
- 微型维度的局限性。微型维度是事先用有可能值的组合加载的,需要考虑每个属性的基数,且必须是枚举值。很多属性可能是非枚举型,比如数值类型,如VIP分数、信用分数等;时间类型,如上架时间、下架时间、变更时间等。
- ETL逻辑复杂。对于分布式系统,生成代理键和使用代理键进行ETL加工都非常复杂,ETL开发和维护成本过高。破坏了维度的可浏览性。买家维度和微型维度通过事实表建立联系,无法基于VIP等级、信用等级进行浏览和统计。可以通过在买家维度中添加引用微型维度的外键部分来解决此问题,但带来的问题是微型维度未维护历史信息。
8. 特殊维度
8.1. 递归层次
维度的递归层次,按照层级是否固定分为均衡层次结构和非均衡层次结构。比如类目,有固定数量的级别,分别是叶子类目、五级类目、四级类目、三级类目、二级类目、一级类目;地区,分别是乡镇/街道区县、城市、省份、国家。对于这种具有固定数量级别的递归层次,称为“均衡层次结构”。比如公司之间的关系,每个公司可能存在一个母公司,但可能没有固定的一级、二级等层级关系。对于这种数量级别不固定的递归层次,称为“非均衡层次结构”。
淘宝交易事实表通过叶子类目和类目维表关联,如何统计类目D为21的最近一天的GMV?第一步,获取父类目ID等于20的所有类目,称为子类目。第二步,对于每个子类目,如果为叶子类目,则终止;如果非叶子类目,则此类目D作为父类目D执行第一步,直到找到所有叶子类目,如圣诞服饰(50026579)、台历(121456022)等。将所有叶子类目和交易事实表关联进行统计汇总,即可得到类目D等于21的最近一天的GMV,也就是家具日用类目的最近一天的GMV。在物理实现时,可以使用递归SQL实现,如Oracle中的connect by语句。
通过数据探查得知D等于21的类目属于一级类目(父类目ID等于0),统计其最近一天的GMV的过程,称为上钻;在递归层次中进行上钻和下钻是很常见的。由于很多数据仓库系统和商业智能工具不支持递归SQL,且用户使用递归SQL的成本较高,所以在维度模型中,需要对此层次结构进行处理。
层次结构扁平化
降低递归层次使用复杂度的最简单和有效的方法是层次结构的扁平化,通过建立维度的固定数量级别的属性来实现,可以在一定程度上解决上钻和下钻的问题。对于均衡层次结构,采用扁平化最有效。
对于淘宝商品类目,通过层次结构扁平化之后,类目维表示如图10.4所示。每个类目保存一条记录,并将其所属的各类目层级属性化。其中,对于高层级类目,由于其无低层级类目,则低层级类目置为空值。
如何统计类目ID为21的最近一天的GMV?将淘宝交易事实表通过叶子类目和类目维表的类目D关联之后,限制一级类目D等于21之后进行汇总统计,即可以得到类目ID等于21的最近一天的GMV。其使用方便性得到大大提高,但存在如下三个方面的问题:
- 针对某类目上钻或下钻之前,必须知道其所属的类目层级,然后才能决定限制哪一级类目。如上述示例,限制一级类目D等于21。
- 假设分三级类目统计最近一天的GMV,由于某些叶子类目直接是一级类目或二级类目(比如类目ID等于121456022的类目,其是叶子类目),和交易事实表关联之后,其对应的三级类目为空,导致根据三级类目统计最近一天的GMV时,类目D等于121456022的交易无法被统计到。下游数据统计时,为了规避此问题,如果此类目对应的三级类目为空,则取二级类目;如果二级类目仍为空,则取一级类目。
所以针对此问题,下游数据统计时,类目层次结构扁平化的另一种方式是回填,将类目向下虚拟,具体数据存储示例如表10.13所示,其中粗体部分为回填内容。阿里巴巴中文站的类目体系使用此种方式。
- 扁平化仅包含固定数量的级别,对于非平衡层次结构,可以通过预留级别的方式来解决,但扩展性较差。
层次桥接表
针对层次结构扁平化所存在的问题,可以采用桥接表的方式来解决,不需要预先知道所属层级,不需要回填,也可解决非均衡层次结构的问题。与扁平化方法相比,该方法适合解决更宽泛的分析问题,灵活性好;但复杂性高,使用成本高。仍然以类目为例,模型设计如图10.5所示。对于上面提到的类目,使用树形结构表示如图10.6所示。
针对此类目树,类目桥接表的内容如表10.14所示。
假设针对类目21进行下钻操作,步骤如下:
限制类目表的类目ID等于21,通过类目D和类目桥接表的父类目D关联,使两表建立连接;通过类目桥接表的子类目D和交易事实表的类目D关联,使两表建立连接;接下来即可针对订单事实进行下钻操作。涉及类目桥接表的数据见表10.14中“父类目ID”字段的粗体部分(21)。
假设针对类目50026579进行上钻操作,步骤如下:
限制类目表的类目ID等于50026579,通过类目D和类目桥接表的子类目D关联,使两表建立连接,通过类目桥接表的父类目D关联,使两表建立连接;接下来即可针对订单事实进行上钻操作。涉及类目桥接表的数据见表10.14中“子类目ID”字段的粗体部分(50026579)。可以看到,层次桥接表解决了层次结构扁平化带来的一些问题;但是其加工逻辑复杂,使用逻辑复杂,而且由于事实表和桥接表的多对多关系而带来了双重计算的隐患。在实际应用中可以根据具体的业务需求来选择技术方案,比如在统计分析或报表中,一般都是按照固定级别进行,如按照一级类目统计GMV、按照省份统计GMV等,现在的扁平化设计完全可以满足需求,而不需要引入复杂的桥接表。很多时候,简单、直接的技术方案却是最好的解决方案。
8.2. 行为维度
在阿里巴巴的数据仓库中,存在很多维表,如卖家主营类目维度卖家主营品牌维度、用户常用地址维度等。其中卖家主营类目和主营品牌通过卖家的商品分布和交易分布情况,采用算法计算得到;卖家常用地址通过最近一段时间内物流中卖家的发货地址和买家的收货地址进行统计得到。类似的维度,都和事实相关,如交易、物流等,称之为“行为维度”,或“事实衍生的维度”。
- 按照加工方式,行为维度可以划分为以下几种:另一个维度的过去行为,如买家最近一次访问淘宝的时间、买家最近一次发生淘宝交易的时间等。
- 快照事实行为维度,如买家从年初截至当前的淘宝交易金额、买家信用分值、卖家信用分值等。
- 分组事实行为维度,将数值型事实转换为枚举值。如买家从年初截至当前的淘宝交易金额按照金额划分的等级、买家信用分值按照分数划分得到的信用等级等。
- 复杂逻辑事实行为维度,通过复杂算法加工或多个事实综合加工得到。如前面提到的卖家主营类目,商品热度根据访问、收藏、加入购物车、交易等情况综合计算得到。
对于行为维度,有两种处理方式,其中一种是将其冗余至现有的维表中,如将卖家信用等级冗余至卖家维表中;另一种是加工成单独的行为维表,如卖家主营类目。具体采用哪种方式主要参考如下两个原则:
- 第一,避免维度过快增长。比如对商品表进行了极限存储,如果将商品热度加入现有的商品维表中,则可能会使每日商品变更占比过高,从而导致极限存储效果较差。
- 第二,避免耦合度过高。比如卖家主营类目,加工逻辑异常复杂,如果融合进现有的卖家维表中,那么过多的业务耦合会导致卖家维表刷新逻辑复杂、维护性差、产出延迟等。
8.3. 多值维度
对于多值维度,一种情况是事实表的一条记录在某维表中有多条记录与之对应。比如对于淘宝交易订单,买家一次购买了多种商品,如一件毛衣和两双袜子,称为交易父订单,对于每种商品的交易,称为交易子订单;此交易父订单有两个子订单与之对应。假设设计交易父订单事实表,则对于此事实表的每一条记录,在商品表中都有一到多条记录与之对应。
针对多值维度,常见的处理方式有三种,可以根据业务的表现形式和统计分析需求进行选择。
第一种处理方式是降低事实表的粒度。在淘宝交易中,前台业务和商业智能关注交易子订单,所以在数据仓库模型设计中,将交易订单设计为子订单粒度,对于每个子订单,只有一种商品与之对应。对于其中的事实,则采用分摊到子订单的方式来解决。但很多时候,事实表的粒度是不能降低的,多值维度的出现是无法避免的。
第二种处理方式是采用多字段。比如在房地产销售中,每次合同签订都可能存在多个买受方的情况,如夫妻合买等。对于合同签订事实表,每条记录可能对应多个买受方,而合同已经是此事实中的最细粒度,无法通过降低粒度的方式来解决。由于合同签订的买受人一般不会太多所以一般采用多字段方式。考虑到扩展性,可以通过预留字段的方式,如超过三个买受方时,其余买受方填写至“其他买受方”字段。模型设计如图10.7所示。
第三种处理方式是采用较为通用的桥接表。桥接表方式更加灵活扩展性更好,但逻辑复杂、开发和维护成本较高,可能带来双重计算的风险,选择此方式需慎重。通过在事实表和维表之间开发一个分组表,
通过此分组表建立连接。模型设计如图10.8所示,其中桥接表包含和事实表关联的分组KEY,以及作为买受方维表外键的买受方ID。如果事实表的一条记录对应两个买受方,则桥接表针对这两个买受方建立两条记录,分组KEY相同。
假设根据买受方籍贯统计2015年的合同总金额,如果某合同有两个买受方,籍贯分别是浙江和山东,那么此合同总金额将会分别统计浙江和山东的,造成双重计算。双重计算不一定是错误,对于一些业务需求是合理的;但对于另一些业务需求,则需要规避。
8.4. 多值维度属性
维表中的某个属性字段同时有多个值,称之为“多值属性”。它是多值维度的另一种表现形式。在阿里巴巴的数据仓库中,存在很多维表,如商品SKU维表、商品属性维表、商品标签维表等。每个商品均有一到多个SKU、一到多个属性和一到多个标签,所以商品和SKU、属性、标签都是多对多的关系。淘宝商品SKU和属性信息示例如图10.9所示。
对于多值属性,常见的处理方式有三种,可以根据具体情况进行选择。第一种处理方式是保持维度主键不变,将多值属性放在维度的一个属性字段中。比如对于商品属性(注:此属性是业务上的含义,和维度建模中的维度属性含义不同),可以通过k-v对的形式放在property字段中,数据示例如下:10281239:156426871;137396765:29229:137400766:3226633。此种处理方式扩展性好,但数据使用较为麻烦。
第二种处理方式也是保持维度主键不变,但将多值属性放在维度的多个属性字段中。比如卖家主营类目,由于卖家店铺中可能同时会销售男装、女装、内衣等,所以卖家主营类目可能有多个,但业务需求是只取根据算法计算得到的TOP3。针对此种情况,维度的多值属性字段具体值的数量固定,可以采用多个属性字段进行存储,方便数据统计分析和报表展示。如果多值属性字段具体值的数量不固定,则可以采用预留字段的方式,但扩展性较差。卖家主营类目维度设计如图10.10所示。
第三种处理方式是维度主键发生变化,一个维度值存放多条记录。比如商品SKU维表,对于每个商品,有多少SKU,就有多少记录,主键是商品的D和SKU的D。此种处理方式扩展性好,使用方便,但需要考虑数据的急剧膨胀情况。比如淘宝商品属性表采用了此种处理方式,数据记录达到几百亿的级别。
8.5. 杂项维度
杂项维度是由操作型系统中的指示符或者标志字段组合而成的,一般不在一致性维度之列。比如淘宝交易订单的交易类型字段,包括话费充值、司法拍卖、航旅等类型;支付状态、物流状态等,它们在源系统中直接保存在交易表中。
一个事实表中可能会存在多个类似的字段,如果作为事实存放在事实表中,则会导致事实表占用空间过大;如果单独建立维表,外键关联到事实表,则会出现维度过多的情况;如果将这些字段删除,则会有人不同意。
这时,通常的解决方案就是建立杂项维度,将这些字段建立到一个维表中,在事实表中只需保存一个外键即可。多个字段的不同取值组成一条记录,生成代理键,存入维表中,并将该代理键保存到相应的事实表字段下。建议不要直接使用所有的组合生成完整的杂项维表,在抽取遇到新的组合时生成相应的记录即可。杂项维度的ETL过程比一般的维度略微复杂些。
但在阿里巴巴的实践中,杂项维度不仅包含上述指示符、状态或分类等枚举字段,还包含很多非枚举字类等枚举字段,还包含很多非枚举字段,如交易留言、交易属性(由若干kv对组成)、交易标签(由二进制位表示)等。针对这些字段,不可能生成所有的组合;同时,由于在分布式计算系统中生成代理键的复杂度,一般在逻辑建模中,会使用实体的主键作为杂项维度的主键。只考虑杂项维度,忽略其他维度,如图10.11所示。
但子订单维度一般是逻辑模型,物理实现时不进行物理化,订单杂项维度和其他维度一起,会将维度属性退化至事实表中,详情在事实表中描述。
博文参考
- 《阿里巴巴大数据实战》