大数据数仓(Hive/Spark)中,没有主键外键是常态,而分区键却是标配,通过ETL脚本(如ROW_NUMBER去重、LEFT JOIN过滤孤值)替代数据库约束,平衡性能与数据准确性

数据仓库中为何无主键/外键但有分区键?


在数据仓库(如Hive/Spark)中,不设主键/外键是常态,原因包括:

  1. 性能代价高:分布式环境下全局校验唯一性(主键)或引用完整性(外键)需跨节点通信(Shuffle),大幅降低吞吐量。
  2. 数据特性:数仓存储历史快照(如拉链表),同一业务键可能对应多版本记录,无法满足主键唯一性。
  3. 技术限制:Hive等引擎对主键/外键支持弱(仅元数据标记,不强制校验),实际依赖ETL逻辑保证数据质量。

分区键必不可少,因其:

  • 物理优化:分区对应HDFS目录,实现查询裁剪(减少扫描数据量)和高效生命周期管理(如按天删除)。
  • 无逻辑约束:分区键仅影响存储结构,不校验数据内容,与主键/外键的设计目的不同。

解决方案通过ETL脚本(如ROW_NUMBER去重、LEFT JOIN过滤孤值)替代数据库约束,平衡性能与数据准确性。

实际工作中,建表没有主键和外键是因为什么,分区键有的


思考:

  1. 解构用户的问题

    • 主题:实际工作中,建表没有主键和外键。

    • 附加条件:有分区键。

    • 背景:大数据(Hive/Spark)/数据仓库环境(基于之前关于DWS/ADS/88库/Hadoop集群的对话)。

  2. 识别根本原因(为什么没有主键/外键?)

    • 原因1:数据量和性能。在分布式系统(Hive/Spark)中强制主键/外键需要跨节点网络通信(Shuffle)来验证唯一性和引用完整性。这非常慢,实际上不可行。

    • 原因2:数据特征。数据仓库通常加载来自多个源系统的历史快照(拉链表)。同一业务键可能出现多次(例如,SCD类型2)。因此,业务键不是唯一的。

    • 原因3:事务性VS分析性。OLTP(MySQL/Oracle)需要约束来保证事务一致性。OLAP(Hive/DWS)以最终一致性、批量插入和读取性能为优先。约束成了负担。

    • 原因4:Hive的支持。Hive直到最近(3.0+)才支持主键/外键,且仅在启用ACID时,并且仅用于元数据(优化器并不强制它们,唯一性仅由用户保证)。

  3. 阐明为什么分区键

    • 分区键是物理存储属性(HDFS目录)。它不是逻辑约束(如主键)

    • 目的:分区裁剪(查询优化),管理方便(删除分区 = 删除数据),TTL(生命周期)。

    • 在OLAP中,分区是必需的,因为数据扫描成本很高。

  4. 构建回答结构

    • 直接回答:明确肯定这是数据仓库(特别是Hive/Spark)中的行业标准,并简要定义原因(OLAP vs OLTP)。

    • 为什么没有主键(分解原因)

      • 性能开销(分布式校验成本)。

      • 数据历史性(SCD/拉链表,非唯一业务键)

      • 写入模式(批量追加/覆盖,而非逐行更新)。

      • 引擎支持(Hive缺乏严格强制)。

    • 为什么有分区键(对比)

      • 物理存储和查询优化(分区裁剪)。

      • 数据管理(删除/覆盖分区)。

    • “无主键”如何保证数据质量?(现实世界的解决方案)——由于它们是“逻辑”上需要的。

    • 总结/附录:关联到DWS/ADS表。

  5. 起草过程中的自我修正/审查

    • 我是否提到了“唯一性”不保证? 是的,在“数据历史性”下。

    • 我是否提到Hive不强制要求? 是的。

    • 是否回答了“为什么要分区键”? 是的,强调这是物理切割。

    • 是否需要关联其特定环境? 是的,提到“88库”、“DWS/ADS表”以及“数仓模型开发规范”中要求分区表的部分。

    • 完善措辞:确保不将日志(Redo) 概念与Hive混合,而应侧重于Shuffle/网络开销。主题:数据仓库建模。

  6. 起草回答(内心/草稿)

    • “在大数据数仓中,无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 去重),否则报表数据会翻倍或出错。

内容概要:本文围绕“基于需求侧响应的配电网供电能力综合评估”展开研究,点探讨了价格型需求响应机制对配电网供电能力的影响,并提出了一套科学的综合评估方法。研究构建了一个涵盖一次设备安全、负荷平稳性、电能质量和系统效率等多个维度的评价指标体系,采用熵权法客观确定各指标权,并结合模糊综合评价模型实现双层评分机制,从而定量评估不同运行场景下配电网的承载能力。通过Python编程实现算法仿真,利用算例分析验证了所提模型在不同分布式能源渗透率及多种需求响应策略下的有效性灵敏度,揭示了价格激励措施在提升电网承载力方面的积极作用,为现代配电网的规划、调度运行优化提供了理论依据和技术支撑。; 适合人群:具备一定电力系统基础知识和Python编程能力,从事电力系统规划、运行优化、需求响应等相关领域的科研人员及研究生。; 使用场景及目标:①评估高比例电动汽车、分布式电源接入背景下配电网的实际供电能力;②分析价格型需求响应策略对提升电网承载力的作用效果;③为配电网扩容改造、运行调度和需求管理政策制定提供决策支持; 阅读建议:建议读者结合文中提供的Python代码进行实证复现,点关注熵权法模糊综合评价的实现逻辑,并尝试修改参设置以观察评估结果的变化趋势,加深对模型机理的理解。
源码下载地址: https://pan.quark.cn/s/a4b39357ea24 在Qt应用程序开发过程中,有时我们可能需要构建一个具备特殊视觉效果的窗口,例如设计成没有边框但带有阴影,并且依然允许用户拖动窗口。此类需求通常出现在构建简洁用户界面或定制化窗口观的场景中。标题“Qt(部分)无边框窗口 边框阴影,可以拖动边框,移动窗口”所涵盖的技术要点主要集中于如何在Qt框架内达成这样的功能,尤其是借助winEvent函写来应对特定的Windows平台事件。 让我们深入理解无边框窗口的概念。在Qt环境中,可以通过调整窗口的边框样式来构建无边框窗口。这通常是通过`setWindowFlags()`函完成的,将`Qt::FramelessWindowHint`标志整合到窗口的标志参里。例如: ```cpp setWindowFlags(Qt::CustomizeWindowHint | Qt::Window | Qt::FramelessWindowHint); ``` 这样一来,窗口将丧失标准的边框和标题栏,但依然维持着窗口管理的基本功能,例如最大化、最小化和关闭操作,前提是你也没有移除这些相关标志。 接下来,为了给无边框窗口增添阴影效果,可以利用Qt的QGraphicsDropShadowEffect类。首先创建一个QGraphicsView对象作为窗口的底层容器,然后在其上放置一个QGraphicsProxyWidget用以展示实际的窗口内容。接着,为QGraphicsView施加阴影效果: ```cpp QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffe...
内容概要:本文系统研究了同步电机构网型变流器在电力系统中的频率稳定特性及其多时间尺度交互机理,基于Simulink搭建高保真仿真模型,深入分析两类电源在动态响应、惯量支撑、频率调节能力等方面的差异耦合关系。研究涵盖不同运行工况下的频率波动响应特性,点揭示控制延迟、电气机械动态过程之间的时间尺度耦合机制,探讨构网型变流器在高比例新能源接入背景下对传统同步机主导系统的频率稳定性的影响,评估其替代或协同传统同步机的潜力挑战,为未来电力系统的稳定运行控制策略设计提供理论依据和技术支撑。; 适合人群:具备电力系统分析、自动控制理论及新能源并网技术背景的科研人员、高校研究生及电力工程技术人员;熟悉Simulink仿真环境者更佳; 使用场景及目标:①深入理解同步电机构网型变流器在频率响应特性上的本质差异及其相互作用机理;②支撑高电力电子化电网的频率稳定性分析新型控制器设计;③为多类型电源协同控制策略的研发仿真验证提供模型基础分析平台; 阅读建议:建议结合Simulink仿真模型进行同步操作,点关注不同时间尺度动态过程的建模方法敏感性分析,深入探究频率稳定性的内在机理,全面把握构网型控制在提升系统稳定性方面的优势潜在局限。
代码转载自:https://pan.quark.cn/s/db56051ee6da 依据所提供的文档资料,能够归纳出以下“寻求一个字符串中连续出现频率最高的子串”相关的基础知识: ### 一、问题的阐述剖析 #### 1.1 问题背景 在计算机科学领域中,字符串操作是一项普遍且关的工作。本议题聚焦于给定字符串,识别其中连续出现频率最高的子串。 #### 1.2 问题陈述 假定存在一个输入字符串 `str`,目标在于找出该字符串中出现频率最高的一个或多个连续子串,并统计它们的出现频次。 #### 1.3 输入输出格式 - **输入**:一个字符串 `str`。 - **输出**:连续出现频率最高的子串及其对应的出现次。 ### 二、算法的构思执行 #### 2.1 基本理念 遍历字符串的所有可能子串,并借助某种数据结构来追踪每个子串的出现频次。通过对比所有子串的出现频次,从而识别出出现频次最高的子串。 #### 2.2 具体执行步骤 1. **初始化**:设定一个字符串 `str` 来存储输入的字符串,以及一个辅助变量 `tep` 来暂存当前子串。 2. **层循环**:从字符串长度减1至1的逆向遍历,每次循环的 `i` 代表子串的长度。 3. **内层循环**:从0至字符串长度减当前子串长度的遍历,每次循环的 `j` 指示子串的起始位置。 4. **子串提取**:运用 `substr` 方法从位置 `j` 开始截取长度为 `i` 的子串并存储至 `tep` 中。 5. **子串检测**:借助 `find` 和 `rfind` 方法分别确定子串在字符串中的初始出现位置 `t` 最终出现位置 `num`。 6. **判定条件**:若 `t` ...
源码下载地址: https://pan.quark.cn/s/a4b39357ea24 在Linux操作系统平台上进行C++语言开发,达成串行通信功能是一项核心且关的技能,特别是在嵌入式系统设计、设备管理或物联网解决方案中。此"Linux下C++实现简易串口交互"范例展示了一个基础性的架构,旨在协助程序员了解怎样运用C++计算机的串行端口(COM端口)进行互动,完成数据的发送及接收任务。接下来将详尽阐述相关技术要点。 1. **串行通信原理**: 串行通信是一种历史悠久的通信机制,借助串行接口来传输信息。在Linux环境中,串行端口通常被映射为/dev/ttySx的路径,其中x代表端口的编号,例如/dev/ttyS0或/dev/ttyUSB0等。串行通信所涉及的要参包含波特率、数据、停止位及校验类型等。 2. **C++系统接口调用**: 若要在C++中操作串口,必须借助系统级调用或第三方库。本范例可能直接运用了包含在<termios.h>头文件中的函,比如使用tcgetattr()和tcsetattr()来配置串口特性,open()和close()用于串口的开启关闭,以及write()和read()负责数据的发送接收。 3. **<termios.h>中的结构体**: struct termios结构体是控制串口行为的决定性组件,它包含了串口的多种配置选项,如波特率(Baud Rate)、数据位(Data Bits)、停止位(Stop Bits)和校验位(Parity Bit)等。程序员需要通过cfsetispeed()和cfsetospeed()来设定输入和输出的波特率,而c_cflag字段则用于设定其他串口配置。 4. **串口初始化...
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星汉灿烂星河

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值