维度表设计——什么字段该冗余、什么该 JOIN 进来?
本文目录
先说结论
冗余的唯一理由是『减少下游 JOIN』。如果一个字段 90% 的查询都要 JOIN 它进来,那就冗余到维度表里;如果只有一两个报表用,就让它们 JOIN。
变化频率是冗余决策的第一原则。稳定字段(国家编码、性别)放心冗余;频繁变化的字段(VIP 等级、会员积分)宁可不冗余,因为变化一次要全表重写。
高基数维度(百万级以上)要独立一张表。比如商品维度、设备维度不能直接冗余到用户维度里,因为会把维度表炸成事实表。
先把这段讲顺;面试官追问时,再展开下面的原理和细节。
详细讲解
面试进入 20 分钟后通常会拐到建模话题,第一道必问的建模题就是维度表设计。面试官会抛出一个场景(比如『设计一张用户维度表』),看你怎么决策字段怎么放、冗余到哪、粒度多粗。回答『就放用户基础信息』太平,回答『所有能想到的字段都冗余进来』太粗。真正考的是你对『冗余成本 vs 查询性能』这个 trade-off 的理解。
业务场景:我在某短视频平台的用户维度表设计
我早年在某短视频平台做用户维度表时,核心诉求有两个:(1)80% 的看板都要按 country/city/age_group/device_model 分析,这些字段要能秒级 group by;(2)用户等级每周变一次,不能因为等级变了就重刷历史数据。所以我把字段分成了三类:冗余、按日快照、不冗余。
-- 用户维度表 dim_user
CREATE TABLE dim_user (
uid BIGINT COMMENT '用户ID(主键)',
-- 【冗余类:稳定字段,90%+ 查询用到】
gender STRING COMMENT '性别(注册时定,不变)',
register_country STRING COMMENT '注册国家',
register_city STRING COMMENT '注册城市',
age_group STRING COMMENT '年龄段(10岁区间)',
first_device_model STRING COMMENT '首次使用设备型号',
-- 【快照类:会变但要保留历史】
level INT COMMENT '当前等级(每周变,用快照)',
vip_status STRING COMMENT '当前VIP状态',
dt STRING COMMENT '快照日期,分区键'
) PARTITIONED BY (dt STRING);
-- 【不冗余类】:
-- last_login_time、current_coins、friend_count 等
-- 这些字段变化太频繁,放事实表或每次 join
分区表每天一份快照是我们常用的做法。下游要看『2024-03-15 的用户分布』就读 dt=2024-03-15 的分区,要看『当前分布』就读最新分区。这样等级变了只影响新快照,历史数据永远不动。
段落一:冗余 vs 不冗余的三条判断标准
| 判断维度 | 适合冗余 | 不适合冗余 |
|---|---|---|
| 使用频率 | 80%+ 的下游查询会用到 | 偶尔用到,2-3 个报表而已 |
| 变化频率 | 月级以上才变 | 天级甚至小时级变化 |
| 基数 | 低基数(几十到几万) | 高基数(百万以上) |
| 是否有历史诉求 | 不需要保留历史 | 需要历史(用 SCD 方案) |
| 字段计算成本 | 原值直接存 | 需要复杂计算派生的 |
经验规则:满足 4-5 条对勾的字段就冗余,满足 2-3 条的字段放快照维度,只满足 1 条或 0 条的字段不冗余。
段落二:缓慢变化维(SCD)的四种方案
维度字段会变怎么办?这就是 SCD(Slowly Changing Dimension)。四种标准方案,面试常考 SCD Type 1 / Type 2。
| SCD 类型 | 做法 | 优点 | 缺点 |
|---|---|---|---|
| Type 1 | 直接覆盖,不保留历史 | 简单、表小 | 丢失历史 |
| Type 2 | 加 start_dt/end_dt 开新行 | 完整历史 | 表膨胀 |
| Type 3 | 加历史字段(prev_value) | 保留上一版 | 只能留一版历史 |
| Type 6(快照) | 每天一个快照分区 | 最直观 | 存储成本高 |
实务里最常用的是 Type 2(拉链表)和 Type 6(快照)。快照简单但存储贵(每天重复存 99% 的相同数据),拉链表存储省但查询复杂。互联网公司普遍用快照——存储便宜、查询简单,代价可控。拉链表第 9 篇会专门讲。
段落三:高基数维度必须独立
新人最爱犯的错是把『商品名 / 设备型号 / 渠道名』直接冗余到 dim_user。这种做法的问题是:维度表会被『用户 × 商品 × 设备』笛卡尔炸开,维度表变成事实表。
-- ❌ 错误:把商品信息冗余到用户维度
CREATE TABLE dim_user_BAD (
uid BIGINT,
last_buy_sku_name STRING, -- 商品名,100 万个 SKU
last_buy_sku_category STRING, -- 品类
last_buy_sku_brand STRING, -- 品牌
-- 每个用户的这三个字段都在频繁变化
-- 维度表膨胀到事实表规模
);
-- ✅ 正确:商品维度独立
CREATE TABLE dim_sku (
sku_id BIGINT PRIMARY KEY,
sku_name STRING, category STRING, brand STRING
);
-- 事实表只存外键
CREATE TABLE fact_order (
order_id BIGINT, uid BIGINT, sku_id BIGINT, ...
);
-- 查询时 JOIN 维度
SELECT u.country, s.brand, SUM(o.amount)
FROM fact_order o
JOIN dim_user u USING (uid)
JOIN dim_sku s USING (sku_id)
GROUP BY u.country, s.brand;
段落四:维度冗余的真实收益计算
什么时候『冗余划算』?一个简单的判断:看冗余后节省的 JOIN 成本 vs 增加的存储成本。我在某短视频平台做过一次测算:把 country 冗余到事实表里让 60% 的报表不需要 JOIN dim_user,节省的计算资源远大于多存 30GB 的代价。
Plain
冗余决策成本分析(某短视频平台真实测算):
冗余 country 字段到 fact_order:
【成本】存储成本:多 30GB / 天 × 365 天 × 低价 S3 ≈ $80/年
【收益】60% 报表少一次 JOIN dim_user
计算成本节省 ≈ $1200/年
结论:冗余
冗余 last_login_time 到 fact_order:
【成本】每小时变一次,要每小时刷 fact_order 维度列
计算成本增加 ≈ $2000/年
【收益】只有 2 个报表用到
结论:不冗余,JOIN 即可
面试高频追问
追问一:你说 80% 查询用到才冗余,怎么知道是 80%?
真实项目里有两种方法:(1)查询日志统计——把最近一个月所有 SQL 日志扒出来,grep 字段名统计出现频率;(2)业务访谈——和主要 BI / 业务方对齐他们常用的分析维度。一般结合起来用。如果是新业务没日志,就按业务方的核心诉求先粗放冗余,上线后再根据实际查询量迭代。
追问二:维度表的主键选什么?自增 ID 还是业务主键?
经典问题。Kimball 教科书推荐代理键(surrogate key,自增 ID),理由是业务主键可能变化。但实务里数仓里直接用业务主键(uid、sku_id)更多,因为事实表关联维度更直观。我的经验是:对于会频繁合并的维度(用户合并、商品合并)用代理键,对于稳定的维度(国家编码、货币编码)直接用业务主键。
追问三:维度表多大就算大?怎么加速查询?
一般 500MB 以上就开始影响性能。加速手段:(1)Broadcast 维度——把小维度表 Broadcast 到所有 executor;(2)冗余高频字段到事实表——减少 JOIN;(3)维度列裁剪——JOIN 时只 SELECT 需要的维度列,不要 JOIN 整张维度表。我在某出海硬件品牌把 Broadcast 阈值从 10MB 调到 200MB,Join 自动变 Broadcast,作业时间减少 40%。
实战踩坑:维度属性变更没做 SCD 的惨案
我在某短视频平台早期有个教训:用户的 country 字段直接覆盖更新(Type 1),没保留历史。结果有一次用户批量『地区迁移』(后台把一批用户的 country 从 TW 改到 CN),第二天业务方炸了——过去 6 个月的地区分布报表全变了,因为历史事实表 JOIN 当前维度表,历史数据被『重分地区』了。
事后改造:dim_user 从 Type 1 改成 Type 6(快照),每天一个快照分区,事实表 JOIN 维度时用『事实发生当天的维度快照』。改造后历史数据稳定,新数据按新口径。教训是:只要业务维度有可能变更,宁可一开始就用快照或拉链,不要 Type 1 覆盖。
-- ✅ 事实表 JOIN 快照维度的标准写法
SELECT f.*, d.country
FROM fact_order f
JOIN dim_user d
ON f.uid = d.uid
AND f.dt = d.dt; -- 关键:按事实发生日关联维度快照
总结
维度表设计的核心是『trade-off 表达能力』。面试时要能说出三件事:(1)冗余的判断标准(频率、变化、基数);(2)变化维度的 SCD 方案选型;(3)一个真实的冗余决策案例(划算 vs 不划算)。能讲清楚这三点,这题就是资深水平。
下一篇:『宽表设计——什么时候该建宽表?什么时候该拆?』——建模题第二道。宽表是一把双刃剑,用得好是王牌,用不好是黑洞。
你们公司的用户维度表有多少字段?有没有那种『传了三任 TL 都没人敢删』的神秘字段?
关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长