数据仓库中为何无主键/外键但有分区键?
在数据仓库(如Hive/Spark)中,不设主键/外键是常态,原因包括:
- 性能代价高:分布式环境下全局校验唯一性(主键)或引用完整性(外键)需跨节点通信(Shuffle),大幅降低吞吐量。
- 数据特性:数仓存储历史快照(如拉链表),同一业务键可能对应多版本记录,无法满足主键唯一性。
- 技术限制:Hive等引擎对主键/外键支持弱(仅元数据标记,不强制校验),实际依赖ETL逻辑保证数据质量。
而分区键必不可少,因其:
- 物理优化:分区对应HDFS目录,实现查询裁剪(减少扫描数据量)和高效生命周期管理(如按天删除)。
- 无逻辑约束:分区键仅影响存储结构,不校验数据内容,与主键/外键的设计目的不同。
解决方案:通过ETL脚本(如
ROW_NUMBER去重、LEFT JOIN过滤孤值)替代数据库约束,平衡性能与数据准确性。
实际工作中,建表没有主键和外键是因为什么,分区键有的
思考:
-
解构用户的问题:
-
主题:实际工作中,建表没有主键和外键。
-
附加条件:有分区键。
-
背景:大数据(Hive/Spark)/数据仓库环境(基于之前关于DWS/ADS/88库/Hadoop集群的对话)。
-
-
识别根本原因(为什么没有主键/外键?):
-
原因1:数据量和性能。在分布式系统(Hive/Spark)中强制主键/外键需要跨节点网络通信(Shuffle)来验证唯一性和引用完整性。这非常慢,实际上不可行。
-
原因2:数据特征。数据仓库通常加载来自多个源系统的历史快照(拉链表)。同一业务键可能出现多次(例如,SCD类型2)。因此,业务键不是唯一的。
-
原因3:事务性VS分析性。OLTP(MySQL/Oracle)需要约束来保证事务一致性。OLAP(Hive/DWS)以最终一致性、批量插入和读取性能为优先。约束成了负担。
-
原因4:Hive的支持。Hive直到最近(3.0+)才支持主键/外键,且仅在启用ACID时,并且仅用于元数据(优化器并不强制它们,唯一性仅由用户保证)。
-
-
阐明为什么有分区键:
-
分区键是物理存储属性(HDFS目录)。它不是逻辑约束(如主键)。
-
目的:分区裁剪(查询优化),管理方便(删除分区 = 删除数据),TTL(生命周期)。
-
在OLAP中,分区是必需的,因为数据扫描成本很高。
-
-
构建回答结构:
-
直接回答:明确肯定这是数据仓库(特别是Hive/Spark)中的行业标准,并简要定义原因(OLAP vs OLTP)。
-
为什么没有主键(分解原因):
-
性能开销(分布式校验成本)。
-
数据历史性(SCD/拉链表,非唯一业务键)。
-
写入模式(批量追加/覆盖,而非逐行更新)。
-
引擎支持(Hive缺乏严格强制)。
-
-
为什么有分区键(对比):
-
物理存储和查询优化(分区裁剪)。
-
数据管理(删除/覆盖分区)。
-
-
“无主键”如何保证数据质量?(现实世界的解决方案)——由于它们是“逻辑”上需要的。
-
总结/附录:关联到DWS/ADS表。
-
-
起草过程中的自我修正/审查:
-
我是否提到了“唯一性”不保证? 是的,在“数据历史性”下。
-
我是否提到Hive不强制要求? 是的。
-
是否回答了“为什么要分区键”? 是的,强调这是物理切割。
-
是否需要关联其特定环境? 是的,提到“88库”、“DWS/ADS表”以及“数仓模型开发规范”中要求分区表的部分。
-
完善措辞:确保不将日志(Redo) 概念与Hive混合,而应侧重于Shuffle/网络开销。主题:数据仓库建模。
-
-
起草回答(内心/草稿):
-
“在大数据数仓中,无PK/FK是常态。”
-
原因:1. 性能杀手(唯一性校验需要全表Sort-Merge或Shuffle,代价极高)。2. 数据特性(如拉链表,同一ID对应多条记录,无法设PK)。3. Hive对约束仅做元数据记录,不强制,仅用于优化器,验证由ETL保证。
-
分区键存在的原因:分区是物理存储层面的(HDFS目录),不校验数据逻辑,只负责裁剪数据量,是查询的基础。
-
“如何保证准确”?通过ETL脚本(去重ROW_NUMBER, left anti join检查孤值)在脚本逻辑中保证,而非依赖数据库约束。
-
这是一个非常典型的“业务数据库(OLTP)”与“数据仓库(OLAP)”在设计哲学上的根本性差异。
你观察得很准:在大数据数仓(Hive/Spark)中,没有主键外键是常态;而分区键却是标配。 这并不是因为开发人员偷懒,而是由底层技术和业务场景决定的。
结合你们的那份《数仓模型开发规范》,我来深度拆解一下背后的原因:
一、 为什么数仓建表没有主键(PK)和外键(FK)?
1. 性能的“致命伤”(分布式校验代价极高)
-
主键意味着要全局去重。在Hive/Spark的分布式架构下,如果要保证几亿甚至几十亿数据里某字段绝对唯一,需要触发大规模的数据混洗(Shuffle),把所有节点的数据拉到一起做全局排序比较。
-
外键意味着要全局关联检查。每插入一条事实表数据,都要去维度表校验是否存在,这在分布式环境下会产生巨大的网络IO开销。
-
结果:如果数仓建了FK,跑一个批处理任务的时间可能会从1小时变成10小时,这在离线数仓中是不可接受的。数仓设计的首要目标是“吞吐量”,而不是“事务实时一致性”。
2. 数据来源决定了“无法唯一”(历史快照与拉链表)
-
数仓里有大量的拉链表(SCD Type 2),用来记录某个客户或合同的状态变化。比如合同号
HT001,可能在昨天是“正常”,今天是“逾期”。 -
如果设
合同号为主键,那么每天新增的历史记录就会报错(主键冲突)。因此,数仓里的表通常没有业务主键,而是通过业务主键 + 分区字段 + 生效时间来联合定位一条记录。
3. Hive 对约束的支持很“弱”(语法兼容,逻辑忽略)
-
虽然在 Hive 3.0+ 之后,语法上支持写
PRIMARY KEY和FOREIGN KEY,但默认只是“元数据标记”(用于告诉优化器或数据血缘工具),并不会在数据写入时进行强制校验。 -
换句话说,即使你写了主键,插入重复数据 Hive 也不会报错,它只会“笑纳”并存进去。既然写了也没用,还增加元数据负担,生产上就干脆不写了。
二、 为什么反而“分区键”必有且极其重要?
分区键(你们规范里的 pt 字段)在数仓里不是“约束”,而是“物理文件夹”。它存在的意义和主键完全不同:
-
它决定数据存哪里:
pt=20260819就是一个HDFS文件夹路径/user/hive/warehouse/表名/pt=20260819/。 -
它是查询的生命线:没有主键,全表扫描还能勉强扛一下;但如果没有分区键限制,跑一个
SELECT COUNT(*)就会触发全表几亿文件的扫描,机器瞬间卡死。 -
它是数据生命周期的管理员:规范里提到的
ALTER TABLE ... DROP PARTITION (pt='20220110'),本质上就是直接删除整个HDFS文件夹,毫秒级删除海量数据。如果不分区,删除旧数据就要逐行扫描删除,代价极大。
三、 既然没有外键,数仓怎么保证数据不错乱?
你可能担心:没有外键,事实表关联维度表会不会出现“孤儿数据”?
答案是:通过“ETL脚本逻辑”来保证,而不是靠“数据库约束”。
-
在你们
tra(转换层)或dws(服务层)的 SQL 脚本中,开发人员会显式地写LEFT JOIN加WHERE 维度主键 IS NOT NULL来过滤脏数据。 -
或者利用
ROW_NUMBER() OVER(PARTITION BY 业务主键 ORDER BY etl_date DESC) = 1来在脚本层实现“去重取最新”,保证数据的唯一性。
这把“保证数据质量”的锁,从数据库的“建表语句”转移到了“数据开发工程师的SQL脚本”里。
四、 💡 给你的“避坑”与汇报建议
当你向领导或同事解释为什么没有主键时,可以这样高情商地回答:
“领导/同事,我们数仓的表设计遵循‘读时校验’原则。为了保障海量数据下 ETL 任务的写入和查询性能,我们没有采用数据库强约束的主外键,而是通过调度任务中的 SQL 逻辑(如去重、左关联过滤)来控制数据质量。同时,我们通过 分区键(pt) 来保证查询性能和数据生命周期管理。”
你在实际写 SQL 关联这几张合同表时,要特别注意:
既然系统没给你加外键,你必须在 JOIN 时自己处理 NULL 值和重复数据(比如合同号关联出多条记录时,记得取 pt 最大或 ROW_NUMBER 去重),否则报表数据会翻倍或出错。
中,没有主键外键是常态,而分区键却是标配,通过ETL脚本(如ROW_NUMBER去重、LEFT JOIN过滤孤值)替代数据库约束,平衡性能与数据准确性&spm=1001.2101.3001.5002&articleId=163886505&d=1&t=3&u=7e819777dcf44da29dd52bd874006d5a)
155

被折叠的 条评论
为什么被折叠?



