DD 数据开发学习站DATA ENGINEERING
模块 04 · 离线计算

Hive 基础

Hive 架构、Metastore、DDL、内外部表,以及本地 Hive 模拟环境。

难度 · 进阶 约 90 分钟 必修 练习 10 项 计划内 刷题 0/3
建议先学:PySpark 实战
本页目录 · 34 节

本章目标:理解 Hive 的表、元数据和执行引擎,能写出库表 DDL、内外部表建表语句,并在本地用 PySpark 的 Hive 兼容模式跑通一套建表和写入。 前置知识:数据仓库入门、PySpark 第一个程序 预计用时:90 分钟

Hive 是大数据离线数仓的“老大哥”。你写的 SQL 会被翻译成分布式任务,跑在成百上千台机器上。本章不要求你装 Hadoop 集群,但要求你能把 Hive 的核心概念讲清楚,并在本地跑出 Hive 风格的建表语句。

1. Hive 是什么

一句话:Hive 是 Hadoop 生态里的离线 SQL 引擎,负责把 SQL 翻译成分布式计算任务。

它解决三个问题:

  1. 让会 SQL 的人直接分析海量数据,不用写 Java MapReduce。
  2. 用统一的 Metastore 管理表结构,让不同引擎都能读同一张表。
  3. 把数据组织成库、表、分区,方便按天扫描和批量加工。

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. 常见坑

  1. 分隔符和文件不一致:CSV 用逗号,建表写了 \t,所有字段变成一列。
  2. 分区字段又写进普通字段:建表报错或查询出现重复列,分区字段只能放在 PARTITIONED BY。
  3. 用 INSERT INTO 跑每日任务:重跑一次数据翻倍,应该用 INSERT OVERWRITE。
  4. DROP 内部表导致数据丢失:ODS 必须用外部表。
  5. 把 ODS 字段建成 INT:源数据有空串或脏字符,加载直接变 NULL,先用 STRING 接住。
  6. 忘了 enableHiveSupport():Spark 读不到 Hive 表,只能看到临时视图。
  7. 小文件太多:几千个几 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 的写法差异
本章任务0 / 10
本页目录