本章目标:理解 Hive 的表、元数据和执行引擎,能写出库表 DDL、内外部表建表语句,并在本地用 PySpark 的 Hive 兼容模式跑通一套建表和写入。 前置知识:数据仓库入门、PySpark 第一个程序 预计用时:90 分钟
Hive 是大数据离线数仓的“老大哥”。你写的 SQL 会被翻译成分布式任务,跑在成百上千台机器上。本章不要求你装 Hadoop 集群,但要求你能把 Hive 的核心概念讲清楚,并在本地跑出 Hive 风格的建表语句。
1. Hive 是什么
一句话:Hive 是 Hadoop 生态里的离线 SQL 引擎,负责把 SQL 翻译成分布式计算任务。
它解决三个问题:
- 让会 SQL 的人直接分析海量数据,不用写 Java MapReduce。
- 用统一的 Metastore 管理表结构,让不同引擎都能读同一张表。
- 把数据组织成库、表、分区,方便按天扫描和批量加工。
1.1 Hive 的表不是数据库里的表
这是面试最常考、也最容易讲错的一点:
Hive 表 = HDFS 上的一个目录 + Metastore 里的一份元数据
- HDFS 目录里放的是数据文件,可能是 CSV、Parquet、ORC。
- Metastore 里存表名、字段、类型、分区、存储格式、目录位置。
- 数据本身不在 Hive 里,Hive 只是“管理员 + 翻译官”。
所以“建表”主要是在 Metastore 里写元数据,“导数”主要是把文件放进目录。DROP TABLE 删的也不只是“表结构”,内部表会连目录一起删。
1.2 为什么离线数仓跑在 Hive 上
| 原因 | 说明 |
|---|---|
| SQL 门槛低 | 会 SQL 就能上手,生态成熟 |
| 能存海量数据 | 数据放 HDFS,天然分布式 |
| 存储计算分离 | 数据在 HDFS,任务可以换 MapReduce、Tez、Spark |
| 元数据统一 | Spark SQL、Presto、Trino 可以共享同一个 Metastore |
| 成本低 | 廉价磁盘存历史数据,适合 T+1 批处理 |
Hive 不适合毫秒级查询,也不适合频繁单行更新。报表要秒级响应,通常会把 ADS 结果同步到 MySQL、ClickHouse、Doris 等 OLAP 库。
2. Hive 的架构
2.1 整体结构
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Beeline CLI │ │ JDBC/ODBC │ │ Hue / 调度 │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
└──────────────────┼──────────────────┘
▼
┌─────────────────┐
│ HiveServer2 │ 接收 SQL、鉴权、返回结果
└────────┬────────┘
▼
┌─────────────────┴─────────────────┐
│ │
┌───────┴────────┐ ┌────────┴─────────┐
│ Metastore │ │ 执行引擎 │
│ 表/字段/分区 │ │ MapReduce/Tez/ │
│ 通常存 MySQL │ │ Spark │
└───────┬────────┘ └────────┬─────────┘
│ │
└──────────────┬────────────────────┘
▼
┌──────────────┐
│ HDFS │ 真正的数据文件
└──────────────┘
2.2 客户端
常见入口有 Beeline(命令行)、JDBC/ODBC(程序和 BI 工具)、Hue(网页 SQL 编辑器)和 调度系统(Airflow、DolphinScheduler)。客户端只负责提交 SQL 和取结果,不负责计算。
2.3 Metastore
Metastore 是 Hive 的“数据字典”,保存:
- 数据库、表、字段、类型。
- 分区及其对应的 HDFS 目录。
- 表的存储格式、分隔符、SerDe。
- 表是内部表还是外部表。
生产上 Metastore 通常用 MySQL 或 PostgreSQL,不用内置的 Derby。Derby 只适合本地单机演示,多用户会冲突。
2.4 HDFS
HDFS 负责存文件,默认三副本。Hive 表的数据最终都在 HDFS 目录里:
/warehouse/dw_demo.db/dwd_order_detail/dt=2018-08-01/part-00000.parquet
dw_demo.db 是库目录,dwd_order_detail 是表目录,dt=2018-08-01 是分区目录。分区就是目录,下一章会专门讲。
2.5 执行引擎
Hive 把 SQL 解析成逻辑计划,再交给执行引擎:
| 引擎 | 特点 |
|---|---|
| MapReduce | 第一代引擎,稳定但慢,中间结果落盘多 |
| Tez | 把多个 MR 任务合并成 DAG,比 MR 快 |
| Spark | 内存计算,目前很多公司用 Spark SQL 跑 Hive 表 |
Hive 本身不存数据、不算数据,它是“翻译官 + 管理员”。真正干活的是执行引擎。
3. 库表 DDL、数据类型与分隔符
3.1 建库
CREATE DATABASE IF NOT EXISTS dw_demo
COMMENT '电商数仓演示库'
LOCATION '/warehouse/dw_demo.db';
USE dw_demo;
数据库对应 HDFS 上的一个目录。
3.2 数据类型
| 类型 | 用途 | 注意 |
|---|---|---|
TINYINT / SMALLINT / INT / BIGINT |
整数 | 金额不要用浮点 |
FLOAT / DOUBLE |
浮点数 | 有精度误差,金额慎用 |
DECIMAL(p, s) |
定点数 | 金额推荐 DECIMAL(14, 2) |
STRING |
字符串 | ID、日期、文本都可以放 |
VARCHAR(n) / CHAR(n) |
定长/变长字符串 | 实际使用不如 STRING 多 |
BOOLEAN |
布尔值 | 较少用于大表 |
DATE / TIMESTAMP |
日期时间 | ODS 常用 STRING,DWD 再转 |
ARRAY<T> / MAP<K,V> / STRUCT<...> |
复杂类型 | 配合 explode 使用 |
ODS 层经常把所有字段建成 STRING,原因是源数据可能带脏字符,先原样接住,到 DWD 再 CAST。DWD 的金额用 DECIMAL,避免浮点误差。
3.3 建表与分隔符
CREATE EXTERNAL TABLE IF NOT EXISTS ods_orders (
order_id STRING COMMENT '订单 ID',
customer_id STRING COMMENT '用户 ID',
order_status STRING COMMENT '订单状态',
order_date STRING COMMENT '下单日期',
pay_date STRING COMMENT '支付日期',
delivery_date STRING COMMENT '送达日期',
estimated_date STRING COMMENT '预计送达日期'
)
COMMENT '订单源表'
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n'
STORED AS TEXTFILE
LOCATION '/warehouse/dw_demo.db/ods_orders'
TBLPROPERTIES ('skip.header.line.count' = '1');
FIELDS TERMINATED BY ',':字段用逗号分隔。LINES TERMINATED BY '\n':行分隔符。skip.header.line.count = 1:跳过 CSV 表头。STORED AS TEXTFILE:纯文本存储。
如果 CSV 字段里有逗号或引号,普通 ROW FORMAT DELIMITED 会切错。可以用 OpenCSVSerde:
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.OpenCSVSerde'
WITH SERDEPROPERTIES (
'separatorChar' = ',',
'quoteChar' = '"',
'escapeChar' = '\\'
)
注意:OpenCSVSerde 读出来的列通常都是 STRING,金额要在 DWD 里显式转换。
3.4 什么是 SerDe
SerDe = Serializer + Deserializer,负责“文件字节”和“表行”之间的转换。
- 写入时把行序列化成文件格式。
- 读取时把文件反序列化成列。
不同存储格式配套不同 SerDe:TextFile 用分隔符 SerDe,Parquet/ORC 用自己的 SerDe。你写 STORED AS PARQUET 时,Hive 会自动选对应的 SerDe,一般不用手写。
4. 内部表与外部表
4.1 区别
| 对比项 | 内部表(Managed Table) | 外部表(External Table) |
|---|---|---|
| 建表关键字 | CREATE TABLE |
CREATE EXTERNAL TABLE |
| 数据目录 | 默认在数仓目录下 | 由 LOCATION 指定 |
DROP TABLE |
删元数据 + 删数据目录 | 只删元数据,数据保留 |
| 数据管理 | Hive 负责 | 用户或外部系统负责 |
| 典型用途 | DWD/DWS/ADS 加工表 | ODS 贴源表、共享数据 |
4.2 为什么 ODS 用外部表
ODS 的数据来自业务库或日志,是数仓最原始的资产。如果有人误删了 ODS 表:
- 内部表:元数据和 HDFS 数据一起没了,只能重新从源系统抽,可能已经抽不到历史。
- 外部表:只删了元数据,文件还在,重新建表指向同一目录就能恢复。
所以生产 ODS 几乎一律用外部表。DWD 以后加工出来的表可以用内部表,也可以为了数据湖共享用外部表,取决于公司的存储治理规范。
4.3 手工放文件后修复分区
外部表的文件可能是采集工具直接放进目录的,Metastore 里还没有分区记录。用:
MSCK REPAIR TABLE ods_orders;
它会扫描目录,把新增的分区补进 Metastore。分区很多时这个命令可能较慢,生产上常用 ALTER TABLE ... ADD PARTITION 精确添加。
4.4 查看表信息
DESC FORMATTED ods_orders;
SHOW PARTITIONS ods_orders;
SHOW CREATE TABLE ods_orders;
重点看 Table Type(MANAGED_TABLE 或 EXTERNAL_TABLE)、Location、分区字段。
5. 本地模拟 Hive:PySpark 的 Hive 兼容模式
5.1 为什么不直接装 Hive
真实 Hive 需要 Hadoop、Metastore 数据库、HiveServer2,环境配置失败率很高。项目用 PySpark 的 enableHiveSupport() 在本地模拟:
- 元数据用内置 Derby。
- 数据落本地目录,目录结构模拟 HDFS。
- SQL 语法保持 Hive 风格。
- 不需要装 Hadoop 集群。
项目脚本在 project/hive/run_hive_demo.py,建表和写入 SQL 在 project/hive/hive_ddl.sql。
5.2 运行
先确认 Java 17 和 PySpark 可用:
java -version
python -c "import pyspark; print(pyspark.__version__)"
然后执行:
python project/hive/run_hive_demo.py
脚本会建 ODS 表、分区表,执行动态分区写入,并校验分区数、行数和 GMV。不同数据集的基线不同,具体数字以脚本输出为准;完整数据的逐层对账在项目线里做。
注意:如果报 Java 或
winutils.exe相关错误,先按 PySpark 第一个程序 配好 Java 17 和HADOOP_HOME。本地模拟跑不通不影响理解 Hive 概念,Hive SQL 可以在公司环境验证。
5.3 最小代码骨架
from pyspark.sql import SparkSession
spark = (SparkSession.builder
.appName("hive_local")
.master("local[*]")
.config("spark.sql.warehouse.dir", "project/hive/warehouse")
.config("spark.driver.extraJavaOptions",
"-Dderby.system.home=project/hive/metastore_db")
.enableHiveSupport()
.getOrCreate())
spark.sql("CREATE DATABASE IF NOT EXISTS dw_demo")
spark.sql("SHOW TABLES IN dw_demo").show()
spark.stop()
enableHiveSupport() 是关键,没有它 Spark 只能用临时目录,不能用 Hive 元数据。warehouse.dir 指定数据目录,derby.system.home 指定元数据目录。
注意:Spark 的
USING PARQUET是 Spark DDL 写法;真实 Hive 用STORED AS PARQUET。两者都能建 Parquet 表,面试时要能区分方言。
6. Hive SQL 与标准 SQL 的差异
| 主题 | 标准 SQL / DuckDB | Hive / Spark SQL |
|---|---|---|
| 更新删除 | UPDATE、DELETE 常规支持 |
传统表不支持,ACID 表有限支持 |
| 写入 | INSERT INTO ... VALUES |
支持,但批量场景多用 INSERT ... SELECT |
| 覆盖写 | 无统一语法 | INSERT OVERWRITE TABLE ... |
| 分区 | 无 | PARTITIONED BY,动态分区列放 SELECT 最后 |
| 数组展开 | UNNEST |
LATERAL VIEW explode(...) |
| 日期加减 | date + INTERVAL 1 DAY |
date_add(date, 1)、date_sub(date, 1) |
| 日期格式化 | strftime |
date_format |
| 字符串拼接 | || |
concat、concat_ws |
| 空值函数 | COALESCE |
nvl、coalesce |
| 正则 | REGEXP_MATCHES |
RLIKE、regexp_extract |
| JSON | json_extract |
get_json_object |
| 多行转一行 | STRING_AGG |
concat_ws(',', collect_list(...)) |
| 窗口函数 | 完整支持 | 支持,语法基本一致 |
| 标识符大小写 | 依赖数据库 | 表名列名不敏感,字符串值敏感 |
6.1 没有 UPDATE 怎么办
数仓以批量追加和覆盖为主,不靠逐行更新:
INSERT OVERWRITE TABLE dim_customer
SELECT ...;
需要更新某个分区时:
INSERT OVERWRITE TABLE dwd_order_detail PARTITION (dt = '2018-08-01')
SELECT ... WHERE order_date = DATE '2018-08-01';
6.2 日期函数示例
SELECT
date_add(DATE '2018-06-01', 7) AS plus_7d,
date_sub(DATE '2018-06-01', 1) AS minus_1d,
datediff(DATE '2018-06-10', DATE '2018-06-01') AS diff_days,
date_format(DATE '2018-06-01', 'yyyy-MM') AS month_str,
last_day(DATE '2018-06-01') AS month_end;
6.3 空值与字符串
SELECT nvl(product_category_name, 'unknown') AS category_filled
FROM ods_products;
SELECT
coalesce(customer_city, customer_state, 'unknown') AS region,
concat_ws(',', customer_city, customer_state) AS address
FROM ods_customers;
nvl 只接受两个参数,coalesce 可以接多个。新代码优先用 coalesce。
7. 常见坑
- 分隔符和文件不一致:CSV 用逗号,建表写了
\t,所有字段变成一列。 - 分区字段又写进普通字段:建表报错或查询出现重复列,分区字段只能放在
PARTITIONED BY。 - 用
INSERT INTO跑每日任务:重跑一次数据翻倍,应该用INSERT OVERWRITE。 - DROP 内部表导致数据丢失:ODS 必须用外部表。
- 把 ODS 字段建成 INT:源数据有空串或脏字符,加载直接变 NULL,先用 STRING 接住。
- 忘了
enableHiveSupport():Spark 读不到 Hive 表,只能看到临时视图。 - 小文件太多:几千个几 KB 的文件拖慢任务,后续章节讲合并。
8. 在线练习
| 题目 | 考点 |
|---|---|
| 595. 大的国家 | 简单过滤 |
| 175. 组合两个表 | 多表关联 |
| 196. 删除重复的电子邮箱 | 去重与删除 |
自测:
- 能用自己的话解释“Hive 表 = HDFS 目录 + Metastore 元数据”。
- 能说出
DROP TABLE与DROP EXTERNAL TABLE的区别,并解释 ODS 为什么用外部表。 - 能为一张客户 CSV 写一个外部表 DDL。
- 能说出至少三处 Hive SQL 与 MySQL 的差异。
9. 面试延伸
面试题:Hive 的架构是什么?
是什么:Hive 由客户端、HiveServer2、Metastore、HDFS 和执行引擎组成。
解决什么问题:客户端提交 SQL,HiveServer2 接收并解析,Metastore 提供表和分区元数据,执行引擎把任务跑在集群上,数据最终存放在 HDFS。它让 SQL 用户不必关心 MapReduce 编程。
我的实操边界:我在本地用 PySpark 的 Hive 兼容模式跑过建表、分区写入和 SHOW PARTITIONS;没有在生产环境安装和维护过 HiveServer2 与独立 Metastore。
面试题:内部表和外部表的区别?为什么 ODS 用外部表?
是什么:内部表由 Hive 管理数据和元数据,外部表只管理元数据,数据目录由 LOCATION 指定。
解决什么问题:DROP 内部表会连数据一起删;DROP 外部表只删元数据。ODS 是原始资产,用外部表可以防止误删,也方便多个引擎共享同一份文件。
我的实操边界:项目演示脚本里建过外部表并执行 DROP,验证数据目录仍在;没有在生产环境负责过 ODS 表的重建和恢复。
面试题:Hive 和 Spark SQL 是什么关系?
是什么:Hive 是 Hadoop 上的 SQL 引擎和元数据体系,Spark SQL 是 Spark 的计算引擎,可以连接同一个 Metastore 读写 Hive 表。
解决什么问题:很多公司用 Hive 管元数据、用 Spark 做计算,兼顾生态兼容和内存计算性能。两者不是简单的替代关系。
我的实操边界:我在本地用 enableHiveSupport() 跑 Spark SQL 读写 Hive 风格表;没有对比过 Tez、MapReduce、Spark 三种引擎在 PB 级数据上的性能。
10. 本章验收
- 能用一句话解释 Hive 是什么,并说出“表 = 目录 + 元数据”
- 能画出客户端、HiveServer2、Metastore、HDFS、执行引擎的结构
- 能写出带分隔符、SerDe、跳过表头的外部表 DDL
- 能说清内部表和外部表在
DROP时的区别,以及 ODS 为什么用外部表 - 能用
enableHiveSupport()在本地建库建表并查询 - 能列出至少四处 Hive SQL 与 DuckDB/标准 SQL 的写法差异