本文档基于 Hive 3.x 与 Spark 3.x 版本制定,旨在规范企业级数据仓库建设中 Hive on Spark 的开发流程、数据模型设计、SQL 编写、作业调度等核心环节,实现数据开发的标准化、高效化、可维护性,保障数据质量与作业稳定运行。本规范适用于企业内所有基于 Hive on Spark 进行数据仓库建设、数据开发及数据分析的相关人员。
一、总则
1.1 核心目标
- 统一开发标准,降低团队协作成本,提升开发效率与代码复用性。
- 优化作业性能,减少集群资源消耗,确保作业高效、稳定运行。
- 保障数据质量,确保数据准确、完整、一致,支撑业务决策。
- 规范开发流程,实现作业全生命周期可监控、可追溯、可管理。
- 强化安全合规,保障数据安全,符合企业数据管理与法律法规要求。
1.2 适用范围
本规范覆盖 Hive on Spark 开发全流程,包括但不限于:数据模型设计、数据库与表创建、SQL 脚本编写、作业开发与调试、作业调度配置、性能优化、数据质量保障、安全合规及文档沉淀等环节。
1.3 引用标准
- Apache Hive 3.x 官方文档
- Apache Spark 3.x 官方文档
- 企业数据仓库建设规范
- 企业数据安全管理规定
- 企业调度平台(DolphinScheduler/Airflow)使用规范
二、数据模型设计规范
数据模型设计是数据仓库建设的核心,需遵循"业务驱动、分层设计、高内聚低耦合、可扩展"的原则,确保数据模型能够精准支撑业务分析需求,同时具备良好的维护性与扩展性。
2.1 数据仓库分层规范
企业级数据仓库统一采用五层架构,各层职责与数据处理规范明确,确保数据流转清晰:
| 数据层级 | 层级缩写 | 核心职责 | 数据特性 |
|---|---|---|---|
| 原始数据层 | ODS | 采集原始业务数据,全量/增量同步,保留原始格式 | 未加工、格式多样 |
| 明细数据层 | DWD | 数据清洗、标准化、脱敏,构建原子事实表 | 结构规范、数据干净 |
| 汇总数据层 | DWS | 按业务主题汇总,构建聚合事实表,支撑多维分析 | 粒度适中、查询高效 |
| 维度数据层 | DIM | 构建企业统一维度表,支持缓慢变化维度处理 | 结构稳定、更新频率低 |
| 应用数据层 | ADS | 面向具体业务场景,生成报表数据、指标数据 | 粒度粗、数据量小 |
实际案例:订单业务数据模型分层设计实操
sql
-- 1. ODS层:原始订单支付日志表(全量同步原始数据)
CREATE TABLE dw_order_ods.ods_order_pay_log (
log_id STRING COMMENT '日志ID',
order_id STRING COMMENT '订单ID(原始格式,可能含字母)',
user_id STRING COMMENT '用户ID(原始格式)',
pay_amount STRING COMMENT '支付金额(原始格式,可能含单位)',
pay_time STRING COMMENT '支付时间(原始格式:yyyy-MM-dd HH:mm:ss)',
pay_type STRING COMMENT '支付方式(原始格式:微信/支付宝/银行卡)',
region_code STRING COMMENT '地区编码',
raw_log STRING COMMENT '原始日志内容(JSON格式,保留备用)'
)
COMMENT '订单支付原始日志表'
PARTITIONED BY (dt STRING COMMENT '数据日期,格式:yyyy-MM-dd')
STORED AS ORC
TBLPROPERTIES ('orc.compress'='SNAPPY');
-- 2. DWD层:订单支付明细事实表(清洗标准化)
CREATE TABLE dw_order_dwd.dwd_order_pay_fact (
order_id BIGINT NOT NULL COMMENT '订单ID(标准化为数字)',
user_id BIGINT NOT NULL COMMENT '用户ID(标准化为数字)',
pay_amount DECIMAL(10,2) NOT NULL COMMENT '支付金额(元,去除单位,保留2位小数)',
pay_time TIMESTAMP NOT NULL COMMENT '支付时间(标准化为TIMESTAMP)',
pay_type INT NOT NULL COMMENT '支付方式(标准化:1-微信,2-支付宝,3-银行卡)',
region_code STRING NOT NULL COMMENT '地区编码',
etl_time TIMESTAMP COMMENT 'ETL处理时间'
)
COMMENT '订单支付明细事实表'
PARTITIONED BY (dt STRING COMMENT '数据日期,格式:yyyy-MM-dd', region_code STRING COMMENT '地区编码')
CLUSTERED BY (user_id) SORTED BY (pay_time) INTO 32 BUCKETS
STORED AS ORC
TBLPROPERTIES ('orc.compress'='SNAPPY');
-- 3. DWS层:订单支付汇总表(按地区+支付方式汇总)
CREATE TABLE dw_order_dws.dws_order_pay_summary_day (
dt STRING NOT NULL COMMENT '数据日期',
region_code STRING NOT NULL COMMENT '地区编码',
region_name STRING COMMENT '地区名称',
pay_type INT NOT NULL COMMENT '支付方式',
pay_count BIGINT COMMENT '支付笔数',
total_pay_amount DECIMAL(12,2) COMMENT '支付总金额(元)',
avg_pay_amount DECIMAL(10,2) COMMENT '平均支付金额(元)',
max_pay_amount DECIMAL(10,2) COMMENT '最大支付金额(元)',
min_pay_amount DECIMAL(10,2) COMMENT '最小支付金额(元)'
)
COMMENT '订单支付日汇总表(地区+支付方式维度)'
PARTITIONED BY (dt STRING COMMENT '数据日期,格式:yyyy-MM-dd')
STORED AS ORC
TBLPROPERTIES ('orc.compress'='SNAPPY');
-- 4. DIM层:地区维度表(缓慢变化维度,类型2)
CREATE TABLE dw_common_dim.dim_region_info (
region_code STRING NOT NULL COMMENT '地区编码(主键)',
region_name STRING COMMENT '地区名称',
province_code STRING COMMENT '省份编码',
province_name STRING COMMENT '省份名称',
city_code STRING COMMENT '城市编码',
city_name STRING COMMENT '城市名称',
start_date DATE COMMENT '生效日期',
end_date DATE COMMENT '失效日期',
is_current STRING COMMENT '是否当前生效:Y-是,N-否'
)
COMMENT '地区维度表(支持缓慢变化维度)'
STORED AS ORC
TBLPROPERTIES ('orc.compress'='SNAPPY');
-- 5. ADS层:订单支付报表表(面向业务报表)
CREATE TABLE dw_order_ads.ads_order_pay_report_month (
report_month STRING NOT NULL COMMENT '报表月份(格式:yyyy-MM)',
region_name STRING COMMENT '地区名称',
total_pay_count BIGINT COMMENT '月支付笔数',
total_pay_amount DECIMAL(14,2) COMMENT '月支付总金额(元)',
month_on_month_growth DECIMAL(5,2) COMMENT '月环比增长率(%)'
)
COMMENT '订单支付月报表表'
PARTITIONED BY (report_month STRING COMMENT '报表月份,格式:yyyy-MM')
STORED AS ORC
TBLPROPERTIES ('orc.compress'='SNAPPY');
-- 数据流转说明:
-- 1. 从业务系统采集原始日志到ods_order_pay_log(每日全量)
-- 2. 清洗转换ods数据到dwd_order_pay_fact(每日增量,标准化字段类型、过滤脏数据)
-- 3. 基于dwd表+dim_region_info表汇总到dws_order_pay_summary_day(每日增量)
-- 4. 基于dws表汇总计算到ads_order_pay_report_month(每月增量,计算环比增长率)
-- 5. 业务报表直接查询ads表,查询效率提升10倍+(从扫描1亿条DWD数据变为100条ADS数据)
2.2 数据库与表命名规范
2.2.1 数据库命名
标准 :采用 业务域_数据层级 命名格式,全小写字母,单词间用下划线分隔,长度 ≤ 30 字符,企业级统一前缀:dw_(数据仓库)。
示例:
dw_order_ods(订单域原始数据层数据库)dw_user_dwd(用户域明细数据层数据库)dw_product_dws(商品域汇总数据层数据库)
2.2.2 表命名
标准 :采用 数据层级_业务模块_表类型_粒度(可选) 命名格式,全小写字母,单词间用下划线分隔,长度 ≤ 40 字符,表类型区分标准如下:
| 表类型 | 命名规则 | 示例 |
|---|---|---|
| 事实表 | 后缀 _fact |
dwd_order_pay_fact(订单支付明细事实表) |
| 维度表 | 前缀 dim_ |
dim_region_info(地区维度表) |
特殊要求:临时表仅用于作业内部数据处理,作业执行完成后必须删除,禁止在生产环境长期保留。
2.3 分区与分桶设计规范
2.3.1 分区设计
合理的分区设计可大幅减少 Hive 查询时的扫描数据量,提升 Spark 计算效率,是 Hive on Spark 性能优化的核心手段之一。企业级标准要求如下:
| 设计维度 | 标准要求 |
|---|---|
| 分区字段选择 | 1. 优先选择时间字段(如 dt/ymd),格式统一为 yyyy-MM-dd(日分区)、yyyy-MM(月分区); 2. 次要选择低基数、业务查询高频的维度字段(如 region 地区、biz_line 业务线); 3. 避免使用高基数字段(如 user_id)导致分区过多。 |
| 分区层级 | 最多支持 3 级分区(如 dt + region + biz_line),单表分区总数 ≤ 10000,避免元数据管理开销过大。 |
| 分区管理 | 1. 定期清理过期分区:按各层级数据保留周期,通过 Hive 命令或调度作业自动清理; 2. 禁止全分区扫描:所有查询必须指定分区范围(业务确需的需经技术负责人审批)。 |
示例:订单表分区语句片段
sql
CREATE TABLE dwd_order_pay_fact (
order_id BIGINT COMMENT '订单ID',
user_id BIGINT COMMENT '用户ID',
pay_amount DECIMAL(10,2) COMMENT '支付金额'
)
COMMENT '订单支付明细事实表'
PARTITIONED BY (
dt STRING COMMENT '数据日期,格式:yyyy-MM-dd',
region STRING COMMENT '地区编码'
)
STORED AS ORC
TBLPROPERTIES ('orc.compress'='SNAPPY');
实际案例:分区设计优化实操(订单表分区调整)
sql
-- 1. 初始分区设计(不合理:仅按dt分区,单分区数据量1亿条,查询特定地区数据时扫描全量)
CREATE TABLE dwd_order_pay_fact_bad (
order_id BIGINT COMMENT '订单ID',
user_id BIGINT COMMENT '用户ID',
pay_amount DECIMAL(10,2) COMMENT '支付金额',
region STRING COMMENT '地区编码'
)
PARTITIONED BY (dt STRING COMMENT '数据日期')
STORED AS ORC;
-- 问题:查询2024-05-01北京地区(region_code='110000')数据时,需扫描整个 dt='2024-05-01' 分区(1亿条),执行时间长
-- 2. 优化后分区设计(合理:按dt+region_code二级分区,单分区数据量降至500万条)
CREATE TABLE dwd_order_pay_fact_good (
order_id BIGINT COMMENT '订单ID',
user_id BIGINT COMMENT '用户ID',
pay_amount DECIMAL(10,2) COMMENT '支付金额'
)
PARTITIONED BY (
dt STRING COMMENT '数据日期',
region_code STRING COMMENT '地区编码'
)
STORED AS ORC;
-- 优化效果:查询2024-05-01北京地区数据时,仅扫描 dt='2024-05-01' 且 region_code='110000' 的分区(500万条),扫描数据量减少95%,执行时间从30分钟缩短至2分钟
-- 3. 分区数据加载示例(动态分区)
SET hive.exec.dynamic.partition=true;
SET hive.exec.dynamic.partition.mode=nonstrict;
INSERT OVERWRITE TABLE dwd_order_pay_fact_good PARTITION (dt, region_code)
SELECT
order_id,
user_id,
pay_amount,
dt,
region_code
FROM dw_order_ods.ods_order_pay_log
WHERE dt = '2024-05-01'
AND order_id IS NOT NULL
AND pay_amount > 0;
2.3.2 分桶设计
分桶是将大表按指定字段哈希分片,适用于大表 JOIN 场景,可减少 Spark Shuffle 数据量,提升 JOIN 效率。企业级标准要求如下:
| 设计维度 | 标准要求 |
|---|---|
| 分桶字段选择 | 优先选择 JOIN 高频字段(如 user_id、order_id)、基数适中的字段,确保数据分布均匀。 |
| 分桶数量 | 根据表数据量与集群节点数确定,一般为 2 的幂次方(如 16、32、64),单个分桶大小建议为 100MB~1GB。 |
| 适用场景 | 仅对数据量 ≥ 1GB、JOIN 频繁的核心事实表进行分桶,维度表一般不建议分桶(数据量小,分桶收益低)。 |
示例:用户订单事实表分桶语句片段
sql
CREATE TABLE dwd_user_order_fact (
order_id BIGINT COMMENT '订单ID',
user_id BIGINT COMMENT '用户ID',
product_id BIGINT COMMENT '商品ID',
order_time TIMESTAMP COMMENT '下单时间'
)
COMMENT '用户订单明细事实表'
PARTITIONED BY (dt STRING COMMENT '数据日期,格式: yyyy-MM-dd')
CLUSTERED BY (user_id) SORTED BY (order_time) INTO 32 BUCKETS
STORED AS ORC
TBLPROPERTIES ('orc.compress'='SNAPPY');
实际案例:分桶表 JOIN 优化实操(用户订单表与支付表 JOIN)
sql
-- 1. 未分桶表JOIN(低效: Shuffle数据量大)
-- 订单表(1亿条/天)
CREATE TABLE dwd_user_order_fact_no_bucket (
order_id BIGINT COMMENT '订单ID',
user_id BIGINT COMMENT '用户ID',
order_time TIMESTAMP COMMENT '下单时间'
)
PARTITIONED BY (dt STRING COMMENT '数据日期');
-- 支付表(8000万条/天)
CREATE TABLE dwd_order_pay_fact_no_bucket (
order_id BIGINT COMMENT '订单ID',
user_id BIGINT COMMENT '用户ID',
pay_amount DECIMAL(10,2) COMMENT '支付金额'
)
PARTITIONED BY (dt STRING COMMENT '数据日期');
-- JOIN查询(Shuffle数据量1.8亿条,执行时间40分钟)
SELECT
o.order_id,
o.user_id,
o.order_time,
p.pay_amount
FROM dwd_user_order_fact_no_bucket o
JOIN dwd_order_pay_fact_no_bucket p ON o.order_id = p.order_id
WHERE o.dt = '2024-05-01' AND p.dt = '2024-05-01';
-- 2. 分桶表JOIN(高效: Sort Merge Bucket Join, 无Shuffle)
-- 订单表(分桶: 按order_id分32桶)
CREATE TABLE dwd_user_order_fact_bucket (
order_id BIGINT COMMENT '订单ID',
user_id BIGINT COMMENT '用户ID',
order_time TIMESTAMP COMMENT '下单时间'
)
PARTITIONED BY (dt STRING COMMENT '数据日期')
CLUSTERED BY (order_id) SORTED BY (order_id) INTO 32 BUCKETS
STORED AS ORC;
-- 支付表(分桶:按order_id分32桶,与订单表分桶一致)
CREATE TABLE dwd_order_pay_fact_bucket (
order_id BIGINT COMMENT '订单ID',
user_id BIGINT COMMENT '用户ID',
pay_amount DECIMAL(10,2) COMMENT '支付金额'
)
PARTITIONED BY (dt STRING COMMENT '数据日期')
CLUSTERED BY (order_id) SORTED BY (order_id) INTO 32 BUCKETS
STORED AS ORC;
-- 开启SMBJ优化
SET hive.optimize.bucketmapjoin=true;
SET hive.optimize.bucketmapjoin.sortedmerge=true;
-- JOIN查询(无Shuffle,执行时间5分钟)
SELECT
o.order_id,
o.user_id,
o.order_time,
p.pay_amount
FROM dwd_user_order_fact_bucket o
JOIN dwd_order_pay_fact_bucket p ON o.order_id = p.order_id
WHERE o.dt = '2024-05-01' AND p.dt = '2024-05-01';
-- 优化效果:Shuffle数据量从1.8亿条降至0,执行时间缩短87.5%,集群资源占用减少90%+
2.4 字段设计规范
2.4.1 字段命名与类型选择
标准要求如下:
| 设计维度 | 标准要求 |
|---|---|
| 字段命名 | 1. 采用业务含义命名,全小写字母,单词间用下划线分隔,长度 ≤ 30 字符; 2. 避免使用 Hive/Spark 关键字(如 order、date、user),若必须使用需加下划线后缀(如 order_、date_)。 |
| 数据类型选择 | 遵循"最小必要"原则,推荐类型如下: - 整数型(标识、数量):INT/BIGINT(禁止用 STRING); - 金额、比例:DECIMAL(p,s)(禁止用 FLOAT/DOUBLE,金额建议 DECIMAL(10,2),比例建议 DECIMAL(5,2)); - 日期/时间:DATE/TIMESTAMP(禁止用 STRING,DATE 格式 yyyy-MM-dd,TIMESTAMP 格式 yyyy-MM-dd HH:mm:ss); - 字符串类型(名称、编码):VARCHAR(n)(禁止用无长度限制的 STRING,n 为最大长度)。 |
| 注释要求 | 1. 数据库注释:说明业务域、层级及核心用途; 2. 表注释:说明表主题、数据来源、粒度及用途; 3. 字段注释:说明字段含义、格式、枚举值(如有)、关联关系(如有)。 |
| 约束要求 | 1. 非空约束:主键字段、核心业务字段必须添加 NOT NULL 约束; 2. 主键约束:每张事实表需指定主键(单字段或联合字段),确保数据唯一性。 |
完整示例(建表语句)
sql
-- 数据库创建
CREATE DATABASE IF NOT EXISTS dw_order_dwd
COMMENT '订单域明细数据层数据库,存储订单相关清洗后明细数据';
-- 表创建
CREATE TABLE IF NOT EXISTS dw_order_dwd.dwd_order_pay_fact (
order_id BIGINT NOT NULL COMMENT '订单ID,主键,唯一标识一笔订单',
user_id BIGINT NOT NULL COMMENT '用户ID,关联 dw_user_dim.dim_user_info 表的 user_id 字段',
pay_amount DECIMAL(10,2) NOT NULL COMMENT '支付金额,单位:元,保留2位小数',
pay_time TIMESTAMP NOT NULL COMMENT '支付时间,格式:yyyy-MM-dd HH:mm:ss',
pay_type INT NOT NULL COMMENT '支付方式:1-微信支付,2-支付宝,3-银行卡支付',
region STRING NOT NULL COMMENT '地区编码,关联 dw_common_dim.dim_region_info 表的 region_code 字段'
)
COMMENT '订单支付明细事实表,数据来源于订单系统支付日志,粒度为每笔支付记录,用于支付相关分析'
PARTITIONED BY (dt STRING NOT NULL COMMENT '数据日期,格式:yyyy-MM-dd')
CLUSTERED BY (user_id) SORTED BY (pay_time) INTO 32 BUCKETS
STORED AS ORC
TBLPROPERTIES (
'orc.compress'='SNAPPY',
'comment'='订单支付明细事实表'
);
三、SQL 编写规范
SQL 编写需遵循"语法规范、逻辑清晰、性能最优、易于维护"的原则,结合 HiveSQL 的易用性与 SparkSQL 的高效性,减少集群资源消耗,提升作业执行效率。
3.1 基础语法规范
3.1.1 书写格式规范
企业级统一格式,提升代码可读性与一致性:
| 规范维度 | 具体要求 |
|---|---|
| 关键字规范 | SQL 关键字(SELECT、FROM、JOIN、WHERE、GROUP BY、ORDER BY 等)统一使用大写;表名、字段名、函数名、别名使用小写。 |
| 换行与缩进 | 1. 每个关键字独占一行,语句按逻辑分层缩进(缩进 4 个空格); 2. 多个字段、条件之间用逗号分隔,逗号后换行并缩进; 3. 子查询、JOIN 条件、CASE WHEN 分支等复杂逻辑单独换行缩进。 |
| 注释规范 | 1. 单行注释:使用 -- 注释内容,说明单行代码功能; 2. 多行注释:使用 /* 注释内容 */,说明整个脚本或核心逻辑块的功能、作者、创建时间、修改记录等; 3. 要求:清晰、简洁,避免冗余,核心逻辑必须添加注释。 |
示例(规范格式 SQL)
sql
/*
* 功能:统计2024年5月各地区每日订单支付金额与支付笔数
* 作者:张三(工号001)
* 创建时间:2024-05-01
* 修改记录:2024-05-02 李四(工号002):补充支付方式过滤条件
*/
SELECT
dt,
region,
pay_type,
COUNT(order_id) AS pay_count, -- 支付笔数
SUM(pay_amount) AS total_pay_amount -- 支付总金额(元)
FROM
dw_order_dwd.dwd_order_pay_fact
WHERE
dt BETWEEN '2024-05-01' AND '2024-05-31' -- 统计2024年5月数据
AND pay_type IN (1, 2, 3) -- 仅统计有效支付方式
AND pay_amount > 0 -- 过滤异常支付金额(≤0)
GROUP BY
dt,
region,
pay_type
ORDER BY
dt ASC,
total_pay_amount DESC;
3.2 核心性能优化规范
基于 Hive on Spark 引擎特性,从 SQL 层面进行针对性优化,减少 Shuffle 数据量、提升并行度、规避数据倾斜,是提升作业性能的核心手段。
3.2.1 必做优化措施
所有 Hive on Spark SQL 必须执行以下优化,确保基础性能:
| 优化措施 | 具体要求 | 示例 |
|---|---|---|
| 分区裁剪 | 查询时必须指定分区字段范围,避免全分区扫描。 | WHERE dt = '2024-05-01' 或 dt BETWEEN '2024-05-01' AND '2024-05-31' |
| 列裁剪 | SELECT 语句明确指定所需字段,禁止使用 SELECT *。 | 避免 SELECT * FROM dwd_order_pay_fact,应使用 SELECT order_id, user_id, pay_amount FROM dwd_order_pay_fact |
| 谓词下推 | 过滤条件尽可能提前执行(子查询、JOIN 前过滤),减少后续处理数据量。 | -- 优化前(先 JOIN 后过滤) SELECT opf.order_id, ui.user_name FROM dw_order_dwd.dwd_order_pay_fact opf JOIN dw_user_dim.dim_user_info ui ON opf.user_id = ui.user_id WHERE opf.dt = '2024-05-01'; -- 优化后(先过滤再 JOIN) SELECT opf.order_id, ui.user_name FROM (SELECT * FROM dw_order_dwd.dwd_order_pay_fact WHERE dt = '2024-05-01') opf JOIN dw_user_dim.dim_user_info ui ON opf.user_id = ui.user_id; |
实际案例:执行计划分析与优化实操
sql
-- 1. 查看原始SQL执行计划
EXPLAIN
SELECT
ui.user_name,
SUM(opf.pay_amount) AS total_pay
FROM dw_order_dwd.dwd_order_pay_fact opf
JOIN dw_user_dim.dim_user_info ui ON opf.user_id = ui.user_id
WHERE opf.dt BETWEEN '2024-05-01' AND '2024-05-31'
GROUP BY ui.user_name;
-- 执行计划分析:
-- 若出现"TableScan"(全表扫描)且未显示"PartitionPruning"(分区裁剪),说明未生效分区过滤,需检查WHERE条件是否正确关联分区字段
-- 若出现"Shuffle Join"且用户表数据量较小(<100MB),可优化为Broadcast Join提升效率
-- 2. 优化后SQL及执行计划验证
EXPLAIN
SELECT
ui.user_name,
SUM(opf.pay_amount) AS total_pay
FROM (SELECT user_id, pay_amount FROM dw_order_dwd.dwd_order_pay_fact WHERE dt BETWEEN '2024-05-01' AND '2024-05-31') opf
JOIN /*+ BROADCAST(ui) */ dw_user_dim.dim_user_info ui ON opf.user_id = ui.user_id
GROUP BY ui.user_name;
-- 优化后执行计划应显示:
-- 1. 存在"PartitionPruning",仅扫描2024-05-01至2024-05-31的分区
-- 2. 存在"BroadcastExchange",说明Broadcast Join生效,无Shuffle开销
3.2.2 JOIN 优化规范
JOIN 是 Hive on Spark 作业中最消耗资源的操作之一,需重点优化:
| 优化维度 | 标准要求 | 示例 |
|---|---|---|
| JOIN 顺序 | 多表 JOIN 遵循"小表先 JOIN、过滤后数据量少的表先 JOIN"原则。 | 先 JOIN 维度表(小表),再 JOIN 其他事实表(大表) |
| JOIN 策略选择 | 1. 小表 JOIN 大表:使用 Broadcast Join(小表 < 100MB),通过 /*+ BROADCAST(小表别名) */ 强制指定; 2. 大表 JOIN 大表:有分桶且分桶字段为 JOIN 字段时用 Sort Merge Bucket Join(开启 set hive.optimize.bucketmapjoin=true;),无分桶时用 Shuffle Hash Join。 |
SELECT opf.order_id, ui.user_name, opf.pay_amount FROM dw_order_dwd.dwd_order_pay_fact opf JOIN /*+ BROADCAST(ui) */ dw_user_dim.dim_user_info ui ON opf.user_id = ui.user_id WHERE opf.dt = '2024-05-01'; |
| 数据倾斜处理 | 针对 JOIN 字段热点值,采用热点值随机打散后 JOIN 的方式解决。 | 见下方实操示例 |
| 避免笛卡尔积 | JOIN 时必须指定有效的 ON 条件,禁止无 ON 条件的 JOIN。 | 禁止:SELECT * FROM table1 JOIN table2; 必须添加 ON 条件:SELECT * FROM table1 JOIN table2 ON table1.id = table2.id; |
实际案例:多表 JOIN 顺序优化实操
sql
-- 业务需求:统计各地区、各支付方式的用户支付总额,需关联订单表、用户表、地区表
-- 表数据量说明:
-- dw_order_pay_fact(订单支付表):1亿条/天(大表)
-- dim_user_info(用户表):1000万条(中表)
-- dim_region_info(地区表):1000条(小表)
-- 优化前(不合理JOIN顺序:大表先JOIN中表,再JOIN小表)
SELECT
ri.region_name,
opf.pay_type,
SUM(opf.pay_amount) AS total_pay
FROM dw_order_dwd.dwd_order_pay_fact opf
JOIN dw_user_dim.dim_user_info ui ON opf.user_id = ui.user_id
JOIN dw_common_dim.dim_region_info ri ON ui.region_code = ri.region_code
WHERE opf.dt = '2024-05-01'
GROUP BY ri.region_name, opf.pay_type;
-- 优化后(合理JOIN顺序:小表先JOIN中表,再JOIN大表)
SELECT
ui_ri.region_name,
opf.pay_type,
SUM(opf.pay_amount) AS total_pay
FROM dw_order_dwd.dwd_order_pay_fact opf
JOIN (
-- 小表先JOIN中表,减少中间结果集
SELECT ui.user_id, ri.region_name
FROM dw_user_dim.dim_user_info ui
JOIN /*+ BROADCAST(ri) */ dw_common_dim.dim_region_info ri ON ui.region_code = ri.region_code
) ui_ri ON opf.user_id = ui_ri.user_id
WHERE opf.dt = '2024-05-01'
GROUP BY ui_ri.region_name, opf.pay_type;
-- 优化效果:中间结果集从"1亿条订单+1000万用户"减少为"1000万用户+1000地区",Shuffle数据量降低90%+,执行时间从60分钟缩短至15分钟
3.2.3 聚合函数优化规范
| 优化维度 | 标准要求 | 示例 |
|---|---|---|
| 分组字段优化 | 1. GROUP BY 字段优先选择低基数字段; 2. 避免在 GROUP BY 中使用复杂函数,可提前在子查询中处理。 | -- 优化前(GROUP BY 中使用 SUBSTR) SELECT SUBSTR(dt, 1, 7) AS month, SUM(pay_amount) AS total_pay FROM dw_order_dwd.dwd_order_pay_fact GROUP BY SUBSTR(dt, 1, 7); -- 优化后(子查询中提前处理) SELECT month, SUM(pay_amount) AS total_pay FROM ( SELECT SUBSTR(dt, 1, 7) AS month, pay_amount FROM dw_order_dwd.dwd_order_pay_fact ) t GROUP BY month; |
| 聚合数据倾斜处理 | 针对 GROUP BY 字段热点值,采用热点值随机打散后二次聚合的方式解决。 | 见下方实操示例 |
| 替代 COUNT(DISTINCT) | COUNT(DISTINCT col) 性能较低,数据量大时用 GROUP BY + COUNT 替代。 | -- 优化前(COUNT(DISTINCT)) SELECT dt, COUNT(DISTINCT user_id) AS user_count FROM dw_order_dwd.dwd_order_pay_fact GROUP BY dt; -- 优化后(GROUP BY + COUNT) SELECT dt, COUNT(user_id) AS user_count FROM ( SELECT dt, user_id FROM dw_order_dwd.dwd_order_pay_fact GROUP BY dt, user_id ) t GROUP BY dt; |
实际案例:聚合数据倾斜解决实操(热点用户支付统计)
sql
-- 业务需求:统计2024年5月每日各用户的支付总金额
-- 问题背景:部分用户(如1001、1002)为高频消费用户,单日支付记录超100万条,直接GROUP BY会导致数据倾斜(单Task处理100万+数据,执行超时)
-- 1. 初始SQL(存在数据倾斜)
SELECT
dt,
user_id,
SUM(pay_amount) AS total_pay_amount
FROM dw_order_dwd.dwd_order_pay_fact
WHERE dt BETWEEN '2024-05-01' AND '2024-05-31'
GROUP BY dt, user_id;
-- 问题分析:
-- 执行后查看SparkUI,发现部分Task执行时间超60分钟(正常Task仅5分钟),且数据处理量集中在少数Task(单Task处理100万+条,其他仅10万条)
-- 原因:user_id=1001、1002为热点值,所有该用户的记录被分配到同一个Task处理,导致负载不均
-- 2. 优化后SQL(热点值随机打散+二次聚合)
SELECT
dt,
user_id,
SUM(total_pay) AS total_pay_amount -- 二次聚合:合并打散后的热点数据
FROM (
-- 一次聚合:对热点用户数据随机打散,并行处理
SELECT
dt,
user_id,
-- 热点用户:在user_id后添加随机后缀(0-9),将数据分散到不同Task
-- 非热点用户:保持原user_id不变
CASE
WHEN user_id IN (1001, 1002) THEN CONCAT(user_id, '_', CAST(RAND() * 10 AS INT))
ELSE CAST(user_id AS STRING)
END AS new_user_id,
SUM(pay_amount) AS total_pay
FROM dw_order_dwd.dwd_order_pay_fact
WHERE dt BETWEEN '2024-05-01' AND '2024-05-31'
GROUP BY dt, user_id, new_user_id -- 按打散后的new_user_id分组,分散热点
) t
GROUP BY dt, user_id;
-- 优化原理:
-- 1. 一次聚合:将热点用户1001、1002的记录打散为10个不同的new_user_id(如1001_0、1001_1...1001_9),每个new_user_id对应的数据量约10万条,分散到10个Task并行处理,避免单Task过载
-- 2. 二次聚合:将打散后的同一用户的不同new_user_id数据合并,得到最终的用户支付总金额
-- 优化效果:
-- 热点Task处理数据量从100万+条降至10万条以内,所有Task执行时间均控制在8分钟内,整体作业执行时间从70分钟缩短至15分钟,执行效率提升78%+
3.2.4 窗口函数优化规范
窗口函数(如 ROW_NUMBER()、RANK()、LAG() 等)在企业级数据分析中广泛应用,需关注性能优化与结果准确性:
| 优化维度 | 标准要求 | 示例 |
|---|---|---|
| PARTITION BY 优化 | 优先选择低基数字段作为分区字段,避免高基数字段导致分区过多、并行度异常;若需按高基数字段分区,需合理设置 Spark 并行度参数(spark.sql.shuffle.partitions)。 | -- 合理:按地区(低基数)分区 SELECT *, ROW_NUMBER() OVER (PARTITION BY region ORDER BY pay_time DESC) AS rn FROM dwd_order_pay_fact; -- 需优化:按user_id(高基数)分区,需调整并行度 SET spark.sql.shuffle.partitions=1000; SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY pay_time DESC) AS rn FROM dwd_order_pay_fact; |
| ORDER BY 优化 | 窗口函数内的 ORDER BY 会触发排序操作,数据量大时需避免不必要的排序;若仅需取 Top N,可结合 LIMIT 优化。 | -- 优化前(全量排序) SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY region ORDER BY pay_amount DESC) AS rn FROM dwd_order_pay_fact WHERE dt = '2024-05-01' ) t WHERE rn ≤ 10; -- 优化后(先过滤再排序,减少排序数据量) SELECT * FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY region ORDER BY pay_amount DESC) AS rn FROM ( SELECT * FROM dwd_order_pay_fact WHERE dt = '2024-05-01' LIMIT 1000000 ) t1 ) t2 WHERE rn ≤ 10; |
| 窗口函数复用 | 多个窗口函数若 PARTITION BY 字段相同,可合并为一个窗口定义,减少重复计算。 | -- 优化前(重复定义窗口) SELECT *, ROW_NUMBER() OVER (PARTITION BY region ORDER BY pay_time DESC) AS rn_time, RANK() OVER (PARTITION BY region ORDER BY pay_amount DESC) AS rk_amount FROM dwd_order_pay_fact; -- 优化后(合并窗口定义) SELECT *, ROW_NUMBER() OVER w AS rn_time, RANK() OVER w AS rk_amount FROM dwd_order_pay_fact WINDOW w AS (PARTITION BY region ORDER BY pay_time DESC); |
实际案例:窗口函数优化实操(用户最新支付记录查询)
sql
-- 业务需求:查询2024年5月各用户最新的一笔支付记录
-- 表数据量:dwd_order_pay_fact(1亿条/天,5月共31亿条)
-- 1. 初始SQL(性能低下)
SELECT *
FROM (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY pay_time DESC) AS rn
FROM dw_order_dwd.dwd_order_pay_fact
WHERE dt BETWEEN '2024-05-01' AND '2024-05-31'
) t
WHERE rn = 1;
-- 问题分析:
-- 1. 全量扫描5月31亿条数据,数据处理量极大;
-- 2. 按user_id(高基数,1亿+用户)分区,窗口排序时Shuffle数据量巨大,执行时间超120分钟。
-- 2. 优化后SQL
-- 步骤1:开启合理并行度
SET spark.sql.shuffle.partitions=2000; -- 按集群资源调整,避免并行度过低导致Task过载
-- 步骤2:先按用户+日期分组取每日最新,再取全月最新,减少排序数据量
SELECT *
FROM (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY pay_time DESC) AS rn
FROM (
-- 子查询1:先取每日各用户最新支付记录,数据量降至3100万条(1亿用户 × 31天,去重后)
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY user_id, dt ORDER BY pay_time DESC) AS daily_rn
FROM dw_order_dwd.dwd_order_pay_fact
WHERE dt BETWEEN '2024-05-01' AND '2024-05-31'
) daily_latest
WHERE daily_rn = 1 -- 筛选每日最新
) monthly_latest
WHERE rn = 1; -- 筛选全月最新
-- 优化效果:
-- 排序数据量从31亿条降至3100万条,Shuffle数据量减少99%,执行时间从120分钟缩短至20分钟,性能提升83%
-- 拓展:结合分桶优化窗口函数
-- 若dwd_order_pay_fact按user_id分桶(32桶),可进一步减少Shuffle:
SET hive.optimize.bucketmapjoin=true;
SET spark.sql.shuffle.partitions=32; -- 并行度与分桶数一致
SELECT *
FROM (
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY pay_time DESC) AS rn
FROM dw_order_dwd.dwd_order_pay_fact
WHERE dt BETWEEN '2024-05-01' AND '2024-05-31'
) t
WHERE rn = 1;
3.3 特殊场景 SQL 规范
3.3.1 动态分区插入规范
动态分区用于批量加载数据到分区表,需严格遵循以下规范避免数据错误与性能问题:
- 开启动态分区参数 :必须设置
SET hive.exec.dynamic.partition=true;和SET hive.exec.dynamic.partition.mode=nonstrict;(非严格模式,允许全动态分区); - 控制动态分区数量 :单作业动态分区数 ≤ 1000,避免元数据压力过大;可通过
SET hive.exec.max.dynamic.partitions=1000;和SET hive.exec.max.dynamic.partitions.pernode=100;限制; - 数据过滤:插入前必须过滤脏数据(如 NULL 值、非法日期),避免生成无效分区(如 dt=NULL);
- 分区字段顺序:INSERT 语句中分区字段需放在 SELECT 语句末尾,与 PARTITION 定义顺序一致。
实操示例:动态分区插入订单数据
sql
-- 动态分区插入 DWD 层订单支付表
SET hive.exec.dynamic.partition=true;
SET hive.exec.dynamic.partition.mode=nonstrict;
SET hive.exec.max.dynamic.partitions=1000;
SET hive.exec.max.dynamic.partitions.pernode=100;
INSERT OVERWRITE TABLE dw_order_dwd.dwd_order_pay_fact PARTITION (dt, region_code)
SELECT
CAST(order_id AS BIGINT) AS order_id, -- 数据清洗:标准化订单ID
CAST(user_id AS BIGINT) AS user_id, -- 标准化用户ID
CAST(REPLACE(pay_amount, '元', '') AS DECIMAL(10,2)) AS pay_amount, -- 去除金额单位并标准化类型
CAST(pay_time AS TIMESTAMP) AS pay_time, -- 标准化时间类型
CASE pay_type -- 标准化支付方式
WHEN '微信' THEN 1
WHEN '支付宝' THEN 2
WHEN '银行卡' THEN 3
ELSE 0 -- 异常值标记
END AS pay_type,
CURRENT_TIMESTAMP() AS etl_time, -- ETL处理时间
dt, -- 分区字段1:数据日期(放在末尾)
region_code -- 分区字段2:地区编码(放在末尾,与PARTITION定义顺序一致)
FROM dw_order_ods.ods_order_pay_log
WHERE
dt BETWEEN '2024-05-01' AND '2024-05-31' -- 过滤日期范围
AND order_id IS NOT NULL AND order_id != '' -- 过滤NULL和空字符串订单ID
AND pay_time REGEXP '^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$'; -- 验证时间格式合法性
3.3.2 临时表使用规范
临时表用于存储作业中间结果,需严格控制生命周期与使用场景:
- 命名规范 :必须以
tmp_为前缀,格式为tmp_业务模块_功能_时间戳(如tmp_order_pay_process_20240501),避免同名冲突; - 生命周期 :作业执行完成后必须删除(使用
DROP TABLE IF EXISTS),禁止长期保留;可通过SET hive.temp.table.purge=true;开启临时表自动清理; - 存储格式 :临时表优先使用内存存储(
STORED AS INMEMORY)或 ORC 格式,避免使用文本格式导致性能低下; - 适用场景:仅用于复杂逻辑拆分(如多步骤聚合、多表关联中间结果),简单逻辑禁止使用临时表增加开销。
实操示例:临时表拆分复杂统计逻辑
sql
-- 业务需求:统计2024年5月各地区、各支付方式的支付总额、笔数及环比增长率
-- 拆分逻辑:先统计当月数据,再关联上月数据计算环比,用临时表存储中间结果
-- 1. 创建临时表存储当月统计数据
CREATE TEMPORARY TABLE tmp_order_pay_current_month
STORED AS ORC
AS
SELECT
region_code,
pay_type,
SUM(pay_amount) AS current_total_pay,
COUNT(order_id) AS current_pay_count,
SUBSTR(dt, 1, 7) AS month -- 提取月份
FROM dw_order_dwd.dwd_order_pay_fact
WHERE dt BETWEEN '2024-05-01' AND '2024-05-31'
GROUP BY region_code, pay_type, SUBSTR(dt, 1, 7);
-- 2. 创建临时表存储上月统计数据
CREATE TEMPORARY TABLE tmp_order_pay_last_month
STORED AS ORC
AS
SELECT
region_code,
pay_type,
SUM(pay_amount) AS last_total_pay,
COUNT(order_id) AS last_pay_count,
SUBSTR(dt, 1, 7) AS month
FROM dw_order_dwd.dwd_order_pay_fact
WHERE dt BETWEEN '2024-04-01' AND '2024-04-30'
GROUP BY region_code, pay_type, SUBSTR(dt, 1, 7);
-- 3. 关联临时表计算环比增长率
SELECT
t1.region_code,
t1.pay_type,
t1.current_total_pay,
t1.current_pay_count,
t1.month,
t2.last_total_pay,
t2.last_pay_count,
-- 计算环比增长率(保留2位小数,避免除数为0)
CASE
WHEN t2.last_total_pay = 0 THEN NULL
ELSE ROUND((t1.current_total_pay - t2.last_total_pay) / t2.last_total_pay * 100, 2)
END AS total_pay_mom,
CASE
WHEN t2.last_pay_count = 0 THEN NULL
ELSE ROUND((t1.current_pay_count - t2.last_pay_count) / t2.last_pay_count * 100, 2)
END AS pay_count_mom
FROM tmp_order_pay_current_month t1
LEFT JOIN tmp_order_pay_last_month t2
ON t1.region_code = t2.region_code AND t1.pay_type = t2.pay_type;
-- 4. 作业执行完成后删除临时表
DROP TABLE IF EXISTS tmp_order_pay_current_month;
DROP TABLE IF EXISTS tmp_order_pay_last_month;
四、作业调度与监控规范
企业级 Hive on Spark 作业需通过调度平台(如 DolphinScheduler、Airflow)实现自动化调度,同时建立完善的监控与告警机制,确保作业稳定运行、问题及时发现与处理。
4.1 调度配置规范
4.1.1 基础调度参数配置
核心调度参数需按业务需求与作业特性精准配置,避免资源浪费或作业延迟:
| 参数类型 | 配置要求 | 示例 |
|---|---|---|
| 调度周期 | 1. 日级作业:调度时间设为每日凌晨 2:00-6:00(业务低峰期),依赖上游数据同步完成; 2. 周级/月级作业:分别设为每周一、每月1日凌晨,确保覆盖全量周期数据; 3. 实时作业:按数据增量频率配置(如 5 分钟/15 分钟一次)。 | DWD 层日级清洗作业:每日 3:00 执行;ADS 层月级报表作业:每月 1 日 4:00 执行。 |
| 依赖配置 | 1. 数据依赖:明确依赖上游表的分区数据(如 DWD 作业依赖 ODS 层对应 dt 分区完成); 2. 作业依赖:按数据流转顺序配置(如 DWS 作业依赖 DWD 作业执行成功); 3. 外部依赖:依赖业务系统数据同步作业、第三方接口数据获取作业完成。 | dws_order_pay_summary_day 作业依赖:dw_order_dwd.dwd_order_pay_fact 的 dt 分区存在且状态为 SUCCESS。 |
| 超时时间 | 按作业历史执行时间设置,一般为历史平均执行时间的 2-3 倍;日级作业超时时间 ≤ 2 小时,实时作业超时时间 ≤ 10 分钟。 | DWD 层 1 亿条数据清洗作业:超时时间设为 120 分钟;实时增量同步作业:超时时间设为 5 分钟。 |
| 重试机制 | 1. 重试次数:默认 2 次,间隔 5 分钟(避免瞬时资源不足导致失败); 2. 重试条件:仅针对非业务错误(如集群资源抢占、网络波动),业务错误(如数据缺失、格式异常)不重试。 | 设置重试次数=2,重试间隔=5 分钟;通过作业日志判断错误类型,业务错误触发告警不重试。 |
4.1.2 资源配置规范
根据作业数据量与计算复杂度配置 Spark 资源,避免资源过度分配或不足:
- 基础资源参数 :通过调度平台配置 Spark 驱动(Driver)与执行器(Executor)资源:
- 小作业(数据量 < 1000 万条):Driver 内存 = 2G,Executor 内存 = 4G,Executor 数量 = 2,核心数 = 2;
- 中等作业(数据量 1000 万 - 1 亿条):Driver 内存 = 4G,Executor 内存 = 8G,Executor 数量 = 5-10,核心数 = 4;
- 大作业(数据量 > 1 亿条):Driver 内存 = 8G,Executor 内存 = 16G,Executor 数量 = 10-20,核心数 = 8;
- 动态资源调整 :开启 Spark 动态资源分配(
SET spark.dynamicAllocation.enabled=true;),根据作业执行过程中的资源需求自动调整 Executor 数量; - 资源隔离:核心业务作业与非核心作业分属不同资源队列,避免非核心作业抢占核心资源(通过 YARN 队列配置实现)。
实操示例:调度平台资源配置(DolphinScheduler)
yaml
-- 作业类型:Spark SQL
-- 资源配置:
spark.driver.memory=4g
spark.executor.memory=8g
spark.executor.cores=4
spark.executor.instances=8
spark.dynamicAllocation.enabled=true
spark.dynamicAllocation.minExecutors=2
spark.dynamicAllocation.maxExecutors=15
-- 队列配置:核心业务队列(queue=root.business.core)
-- 超时时间:120分钟
-- 重试次数:2次,重试间隔5分钟
-- 依赖配置:上游作业 dw_order_ods.ods_order_pay_log 同步完成(dt=当前日期)
4.1.3 调度参数动态配置
针对不同周期、不同数据量的作业,支持通过调度平台参数化配置,提升灵活性:
- 日期参数 :使用调度平台内置日期变量(如
${DATE}、${LAST_DATE}、${MONTH}),避免硬编码日期;示例:DWD 层日级作业过滤条件设为dt = '${DATE}',月级作业设为dt BETWEEN '${MONTH_START_DATE}' AND '${MONTH_END_DATE}'; - 环境参数 :区分开发/测试/生产环境,通过参数配置不同环境的数据库前缀(如
dev_dw_、prod_dw_),实现一套脚本多环境复用; - 阈值参数:将数据质量校验阈值(如 NULL 率、波动阈值)配置为参数,便于统一调整,无需修改 SQL 脚本。
实操示例:参数化 SQL 脚本
sql
-- 参数说明:
-- ${DATE}:调度平台传入的当前日期(格式:yyyy-MM-dd)
-- ${NULL_RATE_THRESHOLD}:数据 NULL 率阈值(默认 0.01)
-- ${FLUCTUATION_THRESHOLD}:数据量波动阈值(默认 0.3)
-- 数据清洗作业:参数化日期与质量阈值
INSERT OVERWRITE TABLE dw_order_dwd.dwd_order_pay_fact PARTITION (dt = '${DATE}')
SELECT
order_id,
user_id,
pay_amount,
pay_time,
pay_type,
region_code,
etl_time
FROM dw_order_ods.ods_order_pay_log
WHERE dt = '${DATE}'
AND order_id IS NOT NULL
AND user_id IS NOT NULL;
-- 数据质量校验:引用波动阈值参数
SELECT
COUNT(*) AS data_count,
CASE
WHEN COUNT(*) / LAG(COUNT(*), 1, 0) OVER (ORDER BY '${DATE}') - 1 > ${FLUCTUATION_THRESHOLD}
THEN '异常(波动>30%)'
ELSE '正常'
END AS data_count_check_result
FROM dw_order_dwd.dwd_order_pay_fact
WHERE dt = '${DATE}';
4.2 作业监控与告警规范
建立"全链路监控+分级告警"机制,覆盖作业执行状态、数据质量、资源使用等核心维度,确保问题及时发现与响应:
4.2.1 监控维度与指标
| 监控维度 | 核心指标 | 监控方式 |
|---|---|---|
| 作业执行状态 | 作业成功率、执行时长、重试次数、超时次数 | 调度平台内置监控面板,实时展示作业状态;离线统计每日/每周作业成功率报表。 |
| 数据质量 | 数据量波动、NULL 率、格式错误率、重复率、数据一致性 | 开发专用数据质量校验作业,结果写入质量监控表;对接监控平台展示质量指标。 |
| 资源使用 | CPU 使用率、内存使用率、Shuffle 数据量、磁盘 I/O | 通过 Spark UI、YARN ResourceManager、监控平台(如 Prometheus)实时采集展示。 |
4.2.2 告警分级与处理机制
按问题严重程度划分告警级别,制定差异化处理流程与响应时限,确保核心问题优先解决:
| 告警级别 | 适用场景 | 响应时限 | 通知方式 |
|---|---|---|---|
| P1(紧急) | 核心业务作业失败(如 ADS 层报表作业)、数据质量严重异常(如核心字段 NULL 率 > 10%)、集群故障导致作业无法执行 | 15 分钟内响应,2 小时内解决 | 电话 + 短信 + 企业微信 @所有人 |
| P2(重要) | 非核心业务作业失败(如 DWS 层非关键汇总作业)、数据质量轻微异常(如非核心字段 NULL 率 1%-5%)、作业执行超时但未影响下游 | 30 分钟内响应,4 小时内解决 | 企业微信/钉钉群 @相关人 |
| P3(一般) | 作业执行成功但资源使用过高、数据量波动在阈值边缘(25%-30%)、非核心临时表未及时清理 | 2 小时内响应,24 小时内解决 | 企业微信/钉钉私人消息 |
实操示例:数据质量告警 SQL 脚本
sql
-- 功能:校验 DWD 层订单支付表数据质量,异常时触发 P1/P2 告警
-- 1. 创建临时表存储质量校验结果
CREATE TEMPORARY TABLE tmp_dwd_order_pay_quality_check
AS
SELECT
'${DATE}' AS check_date,
'dw_order_dwd.dwd_order_pay_fact' AS table_name,
COUNT(*) AS total_data_count,
-- 数据量波动校验(与前一日对比)
LAG(COUNT(*), 1, 0) OVER (ORDER BY '${DATE}') AS last_day_count,
CASE
WHEN COUNT(*) = 0 THEN '异常(数据缺失)'
WHEN ABS(COUNT(*) - LAG(COUNT(*), 1, 0) OVER (ORDER BY '${DATE}')) / LAG(COUNT(*), 1, 1) > 0.3
THEN '异常(波动>30%)'
ELSE '正常'
END AS data_count_check_result,
-- 非空字段 NULL 率校验(order_id/user_id/pay_amount 为核心字段)
SUM(CASE WHEN order_id IS NULL THEN 1 ELSE 0 END) / COUNT(*) AS order_id_null_rate,
SUM(CASE WHEN user_id IS NULL THEN 1 ELSE 0 END) / COUNT(*) AS user_id_null_rate,
SUM(CASE WHEN pay_amount IS NULL THEN 1 ELSE 0 END) / COUNT(*) AS pay_amount_null_rate,
CASE
WHEN SUM(CASE WHEN order_id IS NULL OR user_id IS NULL OR pay_amount IS NULL THEN 1 ELSE 0 END) / COUNT(*) > 0.01
THEN '异常(NULL率>1%)'
WHEN SUM(CASE WHEN order_id IS NULL OR user_id IS NULL OR pay_amount IS NULL THEN 1 ELSE 0 END) / COUNT(*) > 0.005
THEN '警告(NULL率0.5%-1%)'
ELSE '正常'
END AS non_null_check_result,
-- 支付时间格式校验(需符合 yyyy-MM-dd HH:mm:ss)
SUM(CASE WHEN pay_time NOT REGEXP '^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$' THEN 1 ELSE 0 END) / COUNT(*) AS pay_time_format_error_rate,
CASE
WHEN SUM(CASE WHEN pay_time NOT REGEXP '^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}$' THEN 1 ELSE 0 END) / COUNT(*) > 0.005
THEN '异常(格式错误率>0.5%)'
ELSE '正常'
END AS pay_time_format_check_result
FROM dw_order_dwd.dwd_order_pay_fact
WHERE dt = '${DATE}';
-- 2. 输出校验结果,异常时触发告警
INSERT OVERWRITE TABLE dw_quality.quality_check_result PARTITION (dt='${DATE}')
SELECT * FROM tmp_dwd_order_pay_quality_check;
-- 3. 触发告警条件:根据异常级别生成告警标识
SELECT
CASE
WHEN data_count_check_result IN ('异常(数据缺失)', '异常(波动>30%)')
OR non_null_check_result = '异常(NULL率>1%)'
THEN 'P1'
WHEN non_null_check_result = '警告(NULL率0.5%-1%)'
OR pay_time_format_check_result = '异常(格式错误率>0.5%)'
THEN 'P2'
ELSE '正常'
END AS alarm_level
FROM tmp_dwd_order_pay_quality_check;
-- 调度平台配置:
-- 若 alarm_level = 'P1',触发 P1 告警(电话+短信+群@所有人);
-- 若为 'P2',触发 P2 告警(群@责任人+短信)
4.3 应急处理规范
针对作业执行失败、数据质量异常等突发情况,制定标准化应急处理流程,最小化业务影响:
4.3.1 常见应急场景及处理方案
| 应急场景 | 处理步骤 | 责任方 |
|---|---|---|
| 核心作业执行超时/失败(资源不足) | 1. 暂停非核心作业,释放集群资源; 2. 调整作业资源配置(增加 Executor 数量/内存); 3. 重启作业; 4. 长期优化:分析作业逻辑,拆分大作业为小作业,优化 SQL 减少 Shuffle。 | 开发工程师 + 运维工程师 |
| 数据缺失(上游数据同步失败) | 1. 联系上游数据同步负责人,确认同步失败原因; 2. 协助修复上游同步作业,补全缺失数据; 3. 数据补全后,重新执行当前作业; 4. 优化依赖配置,增加上游数据同步状态校验(如检查上游分区是否存在)。 | 开发工程师 + 上游业务负责人 |
| 数据错误(SQL逻辑错误/数据清洗不彻底) | 1. 停止作业执行,避免错误数据扩散; 2. 回滚已生成的错误数据(删除异常分区:ALTER TABLE 表名 DROP IF EXISTS PARTITION (dt='${DATE}')); 3. 修正 SQL 逻辑/数据清洗规则; 4. 重新执行作业; 5. 校验数据质量,确认无误后同步下游。 |
开发工程师 |
| 集群故障(节点宕机/服务异常) | 1. 通知运维工程师,紧急修复集群故障; 2. 故障修复后,检查作业依赖的表数据完整性; 3. 重新执行所有受影响作业; 4. 监控作业执行状态,确保全链路数据正常。 | 运维工程师 + 开发工程师 |
| 数据质量异常(NULL率/波动超标) | 1. 提取异常数据样本,分析异常原因(如上游数据问题、清洗规则遗漏); 2. 若为上游数据问题,反馈上游修复;若为清洗规则问题,修正规则后重新执行作业; 3. 重新校验数据质量,直至指标正常; 4. 记录异常原因与解决方案,优化质量校验规则。 | 开发工程师 + 数据质量负责人 |
4.3.2 应急处理复盘要求
应急处理完成后,需在 24 小时内完成复盘总结,形成文档归档:
- 复盘内容:应急场景描述、影响范围(涉及业务、下游作业)、处理过程(时间节点、采取的措施)、问题根因;
- 优化措施:针对根因制定长期优化方案(如优化作业逻辑、完善监控告警规则、调整资源配置、优化数据依赖);
- 文档归档 :复盘文档提交至企业知识库,命名规范为"应急复盘_场景_日期"(如
应急复盘_核心作业超时_20240501),供团队学习参考,避免同类问题重复发生。
五、数据质量与安全规范
数据质量是数据仓库的核心价值保障,数据安全是企业合规运营的基本要求,需严格遵循"全流程管控、分层分级防护"的原则,确保数据准确、完整、安全、合规。
5.1 数据质量保障规范
建立"事前预防、事中控制、事后校验"的全流程数据质量保障体系,覆盖数据从接入到产出的全生命周期:
5.1.1 事前预防:数据模型设计阶段
- 明确数据来源标准:确保数据源可靠,签订数据接入协议,明确数据格式、更新频率、质量要求(如 NULL 率 ≤ 0.5%、格式符合规范);
- 规范字段设计约束:严格遵循字段命名、类型选择规范,对核心字段添加非空、主键、枚举值等约束(如 pay_type 仅允许 1-3 的值),从模型层面保障数据完整性;
- 制定清洗规则预案:针对 ODS 层原始数据可能存在的脏数据(如格式错误、异常值、重复数据),提前制定清洗规则,明确过滤、转换、补全策略。
5.1.2 事中控制:作业执行阶段
- 数据清洗校验:在 SQL 脚本中嵌入清洗逻辑,过滤 NULL 值、非法格式、异常值(如 pay_amount < 0)、重复数据(如通过 DISTINCT 或 GROUP BY 去重);
- 增量数据校验:增量同步作业需校验数据唯一性(如通过 PRIMARY KEY 约束或 UNIQUE 索引),避免重复插入;对增量数据的时间范围进行校验,确保数据不超前、不滞后;
- 中间结果校验:复杂作业拆分后,对关键中间结果(临时表)进行数据质量校验(如统计数据量、检查核心字段 NULL 率),确保每一步数据准确,避免错误累积。
5.1.3 事后校验:数据产出阶段
- 自动化质量校验 :开发专用数据质量校验作业,定期对 DWD/DWS/ADS 层表进行全维度质量检查,核心校验维度包括:
- 完整性:数据量是否符合预期(无缺失、无超额)、核心字段是否完整(NULL 率 ≤ 阈值);
- 准确性:数据格式是否正确(如时间格式、编码格式)、数据值是否在合理范围(如 age 1-120、pay_amount ≥ 0)、数据一致性(如订单表与支付表的订单数匹配);
- 唯一性:无重复数据(如 order_id 唯一);
- 及时性:数据产出时间是否符合要求(如日级作业需在每日 6:00 前完成)。
- 人工抽样校验:针对核心业务表(如 ADS 层报表表),定期(每日/每周)人工抽样检查数据准确性,验证数据与业务实际情况是否一致(如统计的销售额与业务系统对账);
- 数据溯源机制:建立数据溯源链路,记录数据从 ODS 层到 ADS 层的流转过程(来源表、处理逻辑、处理时间),便于质量问题定位与追溯;可通过数据血缘工具(如 Apache Atlas)实现自动化溯源。
实操示例:核心表数据质量自动化校验脚本
sql
-- 功能:自动化校验 DWS 层订单支付汇总表(dws_order_pay_summary_day)的数据质量
CREATE TEMPORARY TABLE tmp_dws_order_pay_quality_check
AS
SELECT
'${DATE}' AS check_date,
'dw_order_dws.dws_order_pay_summary_day' AS table_name,
-- 1. 完整性校验:数据量是否符合预期(与DWD层对应数据量对比,误差≤5%)
(SELECT COUNT(*) FROM dw_order_dws.dws_order_pay_summary_day WHERE dt = '${DATE}') AS dws_data_count,
(SELECT COUNT(*) FROM dw_order_dwd.dwd_order_pay_fact WHERE dt = '${DATE}') AS dwd_data_count,
CASE
WHEN ABS((SELECT COUNT(*) FROM dw_order_dws.dws_order_pay_summary_day WHERE dt = '${DATE}')
- (SELECT COUNT(*) FROM dw_order_dwd.dwd_order_pay_fact WHERE dt = '${DATE}'))
/ (SELECT COUNT(*) FROM dw_order_dwd.dwd_order_pay_fact WHERE dt = '${DATE}') > 0.05
THEN '异常(数据量误差>5%)'
ELSE '正常'
END AS completeness_check,
-- 2. 准确性校验:支付金额合理性(总金额≥0、平均金额在合理范围)
(SELECT SUM(total_pay_amount) FROM dw_order_dws.dws_order_pay_summary_day WHERE dt = '${DATE}') AS total_pay_amount,
(SELECT AVG(avg_pay_amount) FROM dw_order_dws.dws_order_pay_summary_day WHERE dt = '${DATE}') AS avg_pay_amount,
CASE
WHEN (SELECT SUM(total_pay_amount) FROM dw_order_dws.dws_order_pay_summary_day WHERE dt = '${DATE}') < 0
THEN '异常(总支付金额<0)'
WHEN (SELECT AVG(avg_pay_amount) FROM dw_order_dws.dws_order_pay_summary_day WHERE dt = '${DATE}') > 100000
THEN '异常(平均支付金额过高)'
ELSE '正常'
END AS accuracy_check,
-- 3. 唯一性校验:地区+支付方式组合唯一
(SELECT COUNT(*) FROM (
SELECT region_code, pay_type
FROM dw_order_dws.dws_order_pay_summary_day
WHERE dt = '${DATE}'
GROUP BY region_code, pay_type
HAVING COUNT(*) > 1
) t) AS duplicate_count,
CASE
WHEN (SELECT COUNT(*) FROM (
SELECT region_code, pay_type
FROM dw_order_dws.dws_order_pay_summary_day
WHERE dt = '${DATE}'
GROUP BY region_code, pay_type
HAVING COUNT(*) > 1
) t) > 0
THEN '异常(存在重复组合)'
ELSE '正常'
END AS uniqueness_check;
-- 输出校验结果至质量监控表
INSERT OVERWRITE TABLE dw_quality.quality_check_result PARTITION (dt='${DATE}')
SELECT * FROM tmp_dws_order_pay_quality_check;
-- 异常告警触发:若任一校验维度为异常,生成告警标识
SELECT
CASE
WHEN completeness_check = '异常' OR accuracy_check = '异常' OR uniqueness_check = '异常'
THEN 1
ELSE 0
END AS alarm_flag
FROM tmp_dws_order_pay_quality_check;
5.2 数据安全规范
遵循"最小权限原则、分层分级防护、合规可控"的要求,保障数据存储、传输、访问、使用全流程安全,符合《数据安全法》《个人信息保护法》等相关法律法规:
5.2.1 数据访问权限控制
- 角色权限划分 :按"业务域+数据层级+操作类型"划分角色,实现权限精细化管控,常见角色包括:
- 开发角色:拥有开发环境全权限、生产环境只读权限(仅可查询,不可修改/删除);
- 运维角色:拥有生产环境集群、作业管理权限,无数据查询权限;
- 业务分析角色:仅拥有对应业务域 ADS 层表的只读权限;
- 管理员角色:拥有全环境、全权限,负责权限审批、角色管理。
- 细粒度权限控制 :通过 Hive 权限管理功能,实现库、表、分区、列级别的权限控制:
- 库/表级别:控制用户对数据库、表的 CREATE、ALTER、DROP、SELECT、INSERT 等权限;
- 分区级别:对敏感业务数据(如财务数据)按时间分区授权,仅允许访问指定周期的分区;
- 列级别:对敏感字段(如用户手机号、身份证号)隐藏,仅授权给必要角色(如合规审计角色)。
- 权限审批流程:数据访问权限申请需经过"申请人提交 → 业务负责人审批 → 数据安全负责人审批 → 管理员授权"的流程,审批通过后方可授权;
- 权限回收机制:定期(每季度)对所有权限进行 review,回收冗余权限(如员工离职、岗位调整后未回收的权限),确保权限最小化。
实操示例:Hive 权限授权语句
sql
-- 1. 创建业务分析角色(订单域)
CREATE ROLE order_business_analyst;
-- 2. 授予该角色对订单域 ADS 层表的 SELECT 权限
GRANT SELECT ON TABLE dw_order_ads.ads_order_pay_report_month TO ROLE order_business_analyst;
-- 3. 授予该角色对指定分区的访问权限(仅允许访问2024年5月及以后的分区)
GRANT SELECT ON TABLE dw_order_ads.ads_order_pay_report_month PARTITION (report_month >= '2024-05') TO ROLE order_business_analyst;
-- 4. 拒绝该角色访问敏感字段(若表中存在user_id敏感字段)
REVOKE SELECT (user_id) ON TABLE dw_order_ads.ads_order_pay_report_month FROM ROLE order_business_analyst;
-- 5. 将角色授予用户
GRANT ROLE order_business_analyst TO USER business_user1;
5.2.2 敏感数据脱敏规范
针对敏感数据(如个人信息、财务数据、商业秘密),在数据接入、处理、存储、展示全流程进行脱敏处理,确保敏感信息不泄露。敏感数据分类及脱敏方式如下:
| 敏感数据类型 | 脱敏方式 | 适用场景 | 示例 |
|---|---|---|---|
| 个人身份信息(手机号、身份证号、姓名) | 部分替换:保留前N位和后M位,中间用*替换 | 数据开发、业务分析、报表展示 | 手机号:138****5678 身份证号:1101****1234 姓名:张* |
| 财务数据(银行卡号、薪资、交易金额) | 部分替换/范围化:银行卡号保留前6位和后4位,薪资转换为范围(如5k-10k) | 非财务部门分析、公开报表 | 银行卡号:6222****1234 薪资:8k-15k |
| 商业秘密(核心指标、客户名单) | 加密/脱敏展示:采用不可逆加密算法加密,或展示聚合后数据(不展示明细) | 外部合作、非核心业务分析 | 客户名单:加密后存储 指标:仅展示月度汇总,不展示日级明细 |
实操示例:敏感数据脱敏处理 SQL 脚本
sql
-- 功能:将 ODS 层用户原始数据脱敏后加载到 DWD 层,隐藏敏感信息
INSERT OVERWRITE TABLE dw_user_dwd.dwd_user_info_fact PARTITION (dt='${DATE}')
SELECT
user_id, -- 非敏感字段,直接保留
-- 姓名脱敏:保留姓氏,名字用 * 替换
CASE
WHEN user_name IS NOT NULL THEN CONCAT(SUBSTR(user_name, 1, 1), '*')
ELSE NULL
END AS user_name,
-- 手机号脱敏:保留前 3 位和后 4 位,中间 4 位用 * 替换
CASE
WHEN phone IS NOT NULL THEN CONCAT(SUBSTR(phone, 1, 3), '****', SUBSTR(phone, 8, 4))
ELSE NULL
END AS phone,
-- 身份证号脱敏:保留前 4 位和后 4 位,中间 8 位用 * 替换
CASE
WHEN id_card IS NOT NULL THEN CONCAT(SUBSTR(id_card, 1, 4), '********', SUBSTR(id_card, 13, 4))
ELSE NULL
END AS id_card,
-- 银行卡号脱敏:保留前 6 位和后 4 位,中间用 * 替换
CASE
WHEN bank_card IS NOT NULL THEN CONCAT(SUBSTR(bank_card, 1, 6), '****', SUBSTR(bank_card, -4))
ELSE NULL
END AS bank_card,
gender, -- 非敏感字段
-- 年龄范围化脱敏:不展示具体年龄,展示年龄范围
CASE
WHEN age BETWEEN 0 AND 18 THEN '0-18'
WHEN age BETWEEN 19 AND 30 THEN '19-30'
WHEN age BETWEEN 31 AND 50 THEN '31-50'
ELSE '50+'
END AS age_range,
register_time, -- 非敏感字段
CURRENT_TIMESTAMP() AS etl_time
FROM dw_user_ods.ods_user_info_log
WHERE dt = '${DATE}';
5.2.3 数据备份与恢复规范
为应对数据丢失、损坏等风险,建立完善的数据备份与恢复机制,确保数据可追溯、可恢复:
- 备份策略 :
- 核心业务表(DWD/DWS/ADS层):采用"每日增量备份+每周全量备份"的策略,备份数据保留 3 个月;
- ODS层原始数据:采用"每日全量备份",保留 6 个月(原始数据不可再生,延长保留周期);
- 临时表/中间表:无需备份(可通过重新执行作业生成)。
- 备份方式 :
- 通过 Hive 导出工具(EXPORT)将数据导出至 HDFS 备份目录,目录结构规范为"
/backup/数据库名/表名/备份类型/日期/"(如/backup/dw_order_dwd/dwd_order_pay_fact/full/20240501/); - 重要数据同步至离线存储(如对象存储 OSS/S3),实现异地备份,应对集群级故障。
- 通过 Hive 导出工具(EXPORT)将数据导出至 HDFS 备份目录,目录结构规范为"
- 恢复测试:每月进行一次备份数据恢复测试,验证备份数据的完整性、一致性,以及恢复流程的有效性,记录测试结果;
- 恢复流程 :
- 数据丢失/损坏时,先确认丢失数据的范围(表、分区),选择对应的备份类型(增量/全量);
- 通过 Hive 导入工具(IMPORT)将备份数据恢复至目标表,恢复命令示例:
IMPORT TABLE dw_order_dwd.dwd_order_pay_fact PARTITION (dt='20240501') FROM '/backup/dw_order_dwd/dwd_order_pay_fact/increment/20240501/';; - 恢复完成后,执行数据质量校验作业,确认数据准确无误后,同步给下游业务。
六、文档沉淀规范
完善的文档沉淀是知识传承、团队协作、问题追溯的基础,所有 Hive on Spark 开发相关工作均需形成标准化文档,确保信息可共享、可追溯。
6.1 文档类型及核心要求
| 文档类型 | 核心内容 | 责任人 | 归档要求 |
|---|---|---|---|
| 数据模型设计文档 | 1. 业务背景与需求;2. 数据分层说明;3. 表结构详情(字段名、类型、注释、约束);4. 分区/分桶设计;5. 数据流转关系(来源表、下游表);6. 适用场景与业务指标口径。 | 数据模型设计师 + 开发工程师 | 模型设计完成后 1 周内归档;模型变更后更新文档。 |
| SQL 脚本文档 | 1. 脚本功能描述;2. 输入输出表(含数据库名、表名、分区);3. 核心逻辑说明(如清洗规则、聚合逻辑);4. 参数说明(调度参数、阈值参数);5. 执行周期与依赖关系;6. 版本变更记录。 | 开发工程师 | 脚本开发完成后归档至 Git;每次脚本修改后同步更新文档。 |
| 作业调度配置文档 | 1. 作业基本信息(名称、类型、业务域);2. 调度参数配置(周期、超时时间、重试次数);3. 资源配置(Driver/Executor资源、队列);4. 依赖关系(上游数据、上游作业);5. 监控告警规则(告警级别、通知方式)。 | 开发工程师 + 运维工程师 | 调度配置完成后归档至调度平台文档中心;配置变更后同步更新。 |
| 数据质量校验文档 | 1. 校验表及字段;2. 校验维度与指标(完整性、准确性等);3. 校验阈值;4. 校验脚本逻辑;5. 异常处理流程;6. 历史校验结果。 | 开发工程师 + 数据质量负责人 | 校验规则制定完成后归档;校验规则变更后同步更新。 |
| 应急处理复盘文档 | 1. 应急场景描述(问题现象、发生时间);2. 影响范围(涉及业务、下游作业);3. 处理过程(时间节点、采取的措施);4. 问题根因;5. 优化措施;6. 预防方案。 | 应急处理负责人 | 应急处理完成后 24 小时内归档;按季度汇总复盘文档形成经验总结。 |
6.2 文档管理规范
- 文档格式统一:采用企业标准文档格式(如 Markdown),统一字体、字号、标题层级,确保格式规范;
- 命名规范 :文档命名格式为"文档类型_业务域_日期_版本号"(如
数据模型设计文档_订单域_20240501_V1.0),清晰标识文档内容、所属业务、时间与版本; - 版本控制:文档需标注版本号(如 V1.0、V1.1),版本更新时记录更新日志(更新内容、更新人、更新时间),确保版本可追溯;
- 权限管理:按文档类型及业务域划分访问权限,敏感文档(如核心数据模型、商业秘密相关文档)仅授权给必要角色,确保文档安全;
- 定期维护:每季度对归档文档进行一次 review,清理过期文档(如过时的脚本文档、废弃的模型文档),更新变更内容,确保文档时效性与准确性。
七、附则
- 本规范自发布之日起生效,企业内所有基于 Hive on Spark 的数据开发、数据仓库建设、数据分析相关工作均需严格遵循;
- 本规范由企业数据架构团队负责解释与维护,根据业务发展、技术迭代(如 Hive/Spark 版本升级)情况,每半年更新一次,更新后需通知相关人员;
- 各业务域可根据自身业务特性,在本规范基础上制定专项补充规范(如财务域数据安全补充规范、实时数据开发补充规范),但补充规范不得违反本规范的核心要求,且需提交数据架构团队审核备案;
- 建立规范执行监督机制,定期(每季度)检查规范执行情况,对违反本规范导致作业性能低下、数据质量问题、安全事故、文档缺失的,将按企业相关管理制度追究相关人员责任;
- 本规范未尽事宜,参照 Apache Hive 3.x 官方文档、Apache Spark 3.x 官方文档、企业数据仓库建设规范、企业数据安全管理规定执行。
最后,尽信书不如无书,以上规范根据企业实际场景进行合理的调整。