跳到主要内容

Hive 表详解 ⭐️⭐️

内部表、外部表的区别: 重点掌握 ⭐️⭐️⭐️

内部表、外部表的生产应用: 理解记忆 ⭐️⭐️

分区表&分桶表: 重点掌握 ⭐️⭐️⭐️

Hive 有四种基本的表类型:内部表(Managed Table)、外部表(External Table)、分区表(Partitioned Table)和分桶表(Bucketed Table),它们分别对应不同的业务需求。

我们可以将它们从两个维度进行分组理解:

  1. 数据管理维度内部表和外部表,核心区别在于 Hive 是否完全控制数据的生命周期。
  2. 数据组织维度分区表和分桶表,核心目标是通过优化数据存储结构来提升查询性能。

其中,分区表在企业级应用中最为常见,可以说百分之八九十的表都是分区表。

1. 核心维度一:内部表 vs 外部表

这个维度决定了 Hive 与数据之间的“关系”,是 Hive 完全拥有数据,还是仅仅“引用”数据。

1.1 内部表 (Managed Table)

内部表是 Hive 默认的表类型。当创建内部表时,Hive 会(或多或少地)控制着数据的整个生命周期。

理论核心

  • 数据位置:默认情况下,Hive 会将表的数据存储在由配置项 hive.metastore.warehouse.dir 所定义的目录(默认为 /user/hive/warehouse)的子目录下。
  • 生命周期当我们删除一个内部表时,Hive 会同时删除表的元数据和存储在 HDFS 上的数据文件。因此,内部表不适合与其他工具共享数据。

案例实操:内部表

(1)准备原始数据

在服务器路径 /home/hewwen8888/data 下创建 ch4_emp.txt 文件。

[root@hadoop102 datas]$ vim ch4_emp.txt
1001 emp1
1002 emp2
1003 emp3
1004 emp4
1005 emp5
1006 emp6
1007 emp7
1008 emp8
1009 emp9

(2)创建内部表

创建一个普通的内部表,Hive 会在默认仓库路径下为其创建目录。

CREATE TABLE IF NOT EXISTS ds_hive.ch4_emp(
id INT,
name STRING
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
STORED AS TEXTFILE;

(3)加载数据

LOAD DATA LOCAL INPATH "/home/hewwen8888/data/ch4_emp.txt" OVERWRITE INTO TABLE ds_hive.ch4_emp;

(4)查看表的类型

通过 DESC FORMATTED 命令可以看到表的类型为 MANAGED_TABLE

DESC FORMATTED ds_hive.ch4_emp;
-- 输出结果中会包含
-- Table Type: MANAGED_TABLE

(5)删除内部表

执行删除操作后,不仅表的元数据被清除了,HDFS 上对应的数据文件也会被一并删除。

DROP TABLE ds_hive.ch4_emp;

1.2 外部表 (External Table)

对于外部表,Hive 并不认为其完全拥有这份数据,它只负责管理表的元数据。

理论核心

  • 数据位置:创建外部表时,必须使用 LOCATION 关键字明确指定一个 HDFS 路径,数据文件存放在该路径下。
  • 生命周期删除外部表时,Hive 只会删除元数据,不会删除 HDFS 上的数据文件。这使得数据可以在多个表或不同工具之间安全地共享。

案例实操:外部表与内部表的交互

(1)创建一个指向内部表路径的外部表

这是一个有趣的实验:假设我们有一个内部表 ds_hive.ch4_emp_01,它的数据存储在 /user/hive/warehouse/ds_hive.db/ch4_emp_01。现在我们创建一个外部表,让它的 LOCATION 指向同一个路径。

CREATE EXTERNAL TABLE IF NOT EXISTS ds_hive.ch4_emp_03_w(
id INT,
name STRING
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
STORED AS TEXTFILE
LOCATION '/user/hive/warehouse/ds_hive.db/ch4_emp_01';

(2)测试数据同步

  • 向内部表插入数据

    INSERT INTO TABLE ds_hive.ch4_emp_01 SELECT 1, '张三';

    查询外部表 SELECT * FROM ds_hive.ch4_emp_03_w;可以看到外部表数据同步变化

  • 向外部表插入数据

    INSERT INTO TABLE ds_hive.ch4_emp_03_w SELECT 2, '李四';

    查询内部表 SELECT * FROM ds_hive.ch4_emp_01;可以看到内部表数据也同步变化。因为它们指向的是同一个 HDFS 目录。

(3)测试删除操作的影响

现在,我们删除内部表。

DROP TABLE ds_hive.ch4_emp_01;

结果:因为 ds_hive.ch4_emp_01 是内部表,Hive 会将 HDFS 上的数据文件一并删除。此时再查询外部表 ds_hive.ch4_emp_03_w,会发现外部表也查不到数据了,因为它指向的数据目录已经被清空。

1.3 内部表与外部表的区别与应用 (⭐️⭐️⭐️重点掌握)

特性内部表 (Managed Table)外部表 (External Table)
关键字无 (默认)EXTERNAL
数据所有权Hive 拥有并管理数据外部进程或用户拥有数据,Hive 仅引用
DROP 操作删除元数据 + 数据仅删除元数据,数据保留
LOCATION可选,默认在 Hive 仓库必须指定
数据验证遵循 Hive 的 schema on read (读时模式)遵循 Hive 的 schema on read (读时模式)
表结构修改直接修改元数据修改后可能需要 MSCK REPAIR TABLE 来同步分区信息

Schema on Read vs Schema on Write: 传统数据库是 schema on write(写时模式),在写入数据时就进行格式校验。而 Hive 是 schema on read(读时模式),加载数据时(LOAD DATA)只做文件移动或复制,不校验数据格式,只有在真正读取查询时才进行解析和校验。这使得 Hive 的数据加载速度非常快。

生产应用场景 (⭐️⭐️理解记忆)

  • 使用内部表
    • ETL 过程中产生的中间表、临时表。这些表生命周期短,用完后可以方便地连同数据一起清理。
    • 完全由 Hive SQL 生成和管理的结果表
  • 使用外部表
    • 原始日志数据(如 Nginx 日志、埋点日志)。这些数据由其他程序(如 Flume, Spark Streaming)生成,Hive 仅用于分析,绝不能因为误删表而丢失原始数据。
    • 需要被多个工具或框架共享的数据源。例如,一份数据既要用 Hive 分析,也要用 Spark ML 进行模型训练。
    • 需要长期保留的源数据层 (ODS) 表。

2. 核心维度二:分区表 vs 分桶表

这个维度通过在物理上组织数据文件来优化查询性能,避免不必要的全表扫描。

2.1 分区表 (Partitioned Table) (⭐️⭐️⭐️重点掌握)

分区表将一张大表的数据,按照分区键的值,分散存储到 HDFS 上的多个子目录中。查询时,如果 WHERE 条件中包含分区键,Hive 会直接扫描对应的分区目录,从而极大地提升查询效率。

静态分区

静态分区是指在加载数据时,需要手动、明确地指定数据要加载到哪个分区。

(1)创建分区表

使用 PARTITIONED BY 关键字定义分区字段。分区字段是表的“伪列”,它不存储在数据文件中,而是体现在 HDFS 的目录结构上。

-- 创建一个按天分区的表
CREATE TABLE IF NOT EXISTS ds_hive.ch4_t_par_emp(
id INT,
name STRING
)
PARTITIONED BY (day STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
STORED AS TEXTFILE;

(2)加载数据到指定分区

使用 LOAD DATA 时,必须通过 PARTITION 子句指定分区。

LOAD DATA LOCAL INPATH "/home/hewwen8888/data/ch4_emp.txt" OVERWRITE INTO TABLE ds_hive.ch4_t_par_emp PARTITION(day='20250602');

也可以使用 INSERT 语句从其他表加载数据。

INSERT OVERWRITE TABLE ds_hive.ch4_t_par_emp PARTITION (day = '20250602')
SELECT id, name FROM ds_hive.ch4_t_par_emp WHERE day='20240110';

(3)查询分区数据

在查询时带上分区条件,Hive 就会执行“分区裁剪”,只扫描指定分区。

SELECT id, name, day FROM ds_hive.ch4_t_par_emp WHERE day='20250602';

(4)分区管理

  • 查看所有分区
    SHOW PARTITIONS ds_hive.ch4_t_par_emp;
  • 增加单个/多个分区
    -- 增加单个分区
    ALTER TABLE ds_hive.ch4_t_par_emp ADD PARTITION(day='20250603');
    -- 增加多个分区(注意语法,没有逗号)
    ALTER TABLE ds_hive.ch4_t_par_emp ADD PARTITION(day='20250604') PARTITION(day='20250605');
  • 删除单个/多个分区
    -- 删除单个分区
    ALTER TABLE ds_hive.ch4_t_par_emp DROP PARTITION (day='20250604');
    -- 删除多个分区(注意语法,有逗号)
    ALTER TABLE ds_hive.ch4_t_par_emp DROP PARTITION (day='20250605'), PARTITION(day='20250606');

分区修复

若用户手动在 HDFS 上创建/删除分区目录,Hive 的元数据是感知不到的,这会导致元数据与物理路径不一致。

思考:如果手动在 HDFS 添加一个分区目录(如 .../day=20250606)并上传文件,在分区表中能查到新分区的数据吗? 答案:不能,因为元数据中没有这个分区的信息。

这时就需要修复分区,同步元数据和 HDFS 路径。

  • ADD PARTITION:手动添加元数据。
  • DROP PARTITION:手动删除元数据。
  • MSCK REPAIR TABLE (常用):自动扫描 HDFS 路径,修复元数据。
    -- 推荐使用,会自动添加 HDFS 上存在但元数据中缺失的分区
    MSCK REPAIR TABLE ds_hive.ch4_t_par_emp;

    -- 完整语法
    -- MSCK REPAIR TABLE table_name [ADD/DROP/SYNC PARTITIONS];

多级分区

如果单级分区(如按天)的数据量依然很大,可以使用多级分区进一步细化,例如按天再按小时。

-- 创建二级分区表
CREATE TABLE IF NOT EXISTS ds_hive.ch4_t_par_emp2(
id INT,
name STRING
)
PARTITIONED BY (day STRING, hour STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
STORED AS TEXTFILE;

-- 加载数据到二级分区
LOAD DATA LOCAL INPATH "/home/hewwen8888/data/ch4_emp.txt" OVERWRITE INTO TABLE ds_hive.ch4_t_par_emp2 PARTITION(day='20250602', hour='14');

-- 查询二级分区
SELECT * FROM ds_hive.ch4_t_par_emp2 WHERE day='20250602' AND hour='14';

动态分区

当需要一次性将数据插入到多个分区时,如果手动为每个分区写一条 INSERT 语句会非常繁琐。动态分区允许 Hive 根据查询结果的最后一列或几列的值,自动推断并创建分区。

(1)相关参数设置

-- 动态分区功能总开关(默认 true,开启)
SET hive.exec.dynamic.partition=true;

-- 设置为非严格模式,允许所有分区字段都为动态
-- strict 模式下,至少要有一个静态分区
SET hive.exec.dynamic.partition.mode=nonstrict;

-- 其他常用参数
SET hive.exec.max.dynamic.partitions=1000; -- 一条 SQL 最多创建的分区数
SET hive.exec.max.dynamic.partitions.pernode=100; -- 每个节点最多创建的分区数

(2)案例实操:单动态分区

需求:将源表 ch4_t_par_emp_01 的数据,按照 day 字段的值,动态插入到目标分区表 ch4_t_par_emp_dny_01 中。

  • 创建目标分区表
    CREATE TABLE IF NOT EXISTS ds_hive.ch4_t_par_emp_dny_01(
    id INT,
    name STRING
    )
    PARTITIONED BY (day STRING)
    ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t';
  • 执行动态分区插入
    SET hive.exec.dynamic.partition.mode=nonstrict;

    INSERT OVERWRITE TABLE ds_hive.ch4_t_par_emp_dny_01
    PARTITION(day) -- 声明 day 是动态分区字段
    SELECT
    id,
    name,
    day -- 动态分区字段必须是 SELECT 语句的最后一列
    FROM ds_hive.ch4_t_par_emp_01;

    要点:Hive 根据 SELECT 语句中列的位置来推断分区值。PARTITION(day) 对应 SELECT 的最后一列,PARTITION(day, hour) 对应最后两列。列的顺序和数量必须严格匹配。

(3)案例实操:混合分区与全动态分区

  • 半动态分区(一个静态,一个动态)
    -- 目标表有两个分区字段:day, dept
    CREATE TABLE ds_hive.ch4_t_par_emp_dny_02(id INT, name STRING)
    PARTITIONED BY (day STRING, dept STRING);

    -- day 是静态分区,dept 是动态分区
    INSERT OVERWRITE TABLE ds_hive.ch4_t_par_emp_dny_02
    PARTITION(day='20250601', dept)
    SELECT id, name, dept FROM ds_hive.ch4_t_par_emp_04;
  • 全动态分区
    SET hive.exec.dynamic.partition.mode=nonstrict;

    INSERT OVERWRITE TABLE ds_hive.ch4_t_par_emp_dny_02
    PARTITION(day, dept)
    SELECT id, name, day, dept FROM ds_hive.ch4_t_par_emp_04;

2.2 分桶表 (Bucketed Table)

分区提供的是目录级别的粗粒度数据划分。当分区内的数据文件依然很大时,可以使用分桶来进行更细粒度的数据组织。

基本原理:分桶是针对数据文件的。它根据分桶键(通常是表的某个列)的哈希值,对分桶数取模,将数据均匀地写入固定数量的文件(桶)中。

主要优点

  1. 提高 Join 查询效率:如果两个表在 Join 的 key 上都进行了分桶,且分桶数成倍数关系,Hive 可以执行更高效的 Bucket Map Join
  2. 方便高效抽样:可以对桶进行抽样,快速获取数据的样本。

分桶表基本语法

(1)创建分桶表

使用 CLUSTERED BY 指定分桶键,INTO N BUCKETS 指定分桶数量。

CREATE TABLE ds_hive.ch4_emp_buck(
id INT,
name STRING
)
CLUSTERED BY(id) INTO 4 BUCKETS
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
STORED AS TEXTFILE;

(2)加载数据到分桶表

注意:向分桶表加载数据通常需要通过 INSERT ... SELECT 的方式,因为这会触发 MapReduce 作业,在 Reduce 阶段根据哈希值将数据写入不同的桶。直接 LOAD DATA 可能不会正确分桶(取决于 Hive 版本)。

-- 准备一张源数据表
CREATE TABLE ds_hive.ch4_source_data (id INT, name STRING) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t';
LOAD DATA LOCAL INPATH '/home/hewwen8888/data/ch4_emp.txt' INTO TABLE ds_hive.ch4_source_data;

-- 通过 INSERT ... SELECT 方式加载数据到分桶表
INSERT OVERWRITE TABLE ds_hive.ch4_emp_buck
SELECT id, name FROM ds_hive.ch4_source_data;

(3)查看分桶结果

在 HDFS 对应的表目录下,可以看到生成了 4 个数据文件,代表 4 个桶。

3. 表的创建与管理

在理解了核心概念后,我们来系统地看一下表的通用操作。

3.1 完整建表语法

CREATE [EXTERNAL] TABLE [IF NOT EXISTS] table_name
[(col_name data_type [COMMENT col_comment], ...)]
[COMMENT table_comment]
[PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)]
[CLUSTERED BY (col_name, col_name, ...) [SORTED BY (col_name [ASC|DESC], ...)] INTO num_buckets BUCKETS]
[ROW FORMAT row_format]
[STORED AS file_format]
[LOCATION hdfs_path]
[TBLPROPERTIES (property_name=property_value, ...)]

字段解释说明

  • EXTERNAL: 创建外部表。
  • PARTITIONED BY: 创建分区表。
  • CLUSTERED BY: 创建分桶表。
  • SORTED BY: 对桶内数据进行排序(不常用)。
  • ROW FORMAT DELIMITED: 定义行格式和分隔符。
    • FIELDS TERMINATED BY char: 字段间分隔符。
    • COLLECTION ITEMS TERMINATED BY char: 集合(如 ARRAY)元素间分隔符。
    • MAP KEYS TERMINATED BY char: Map 的 Key-Value 间分隔符。
    • LINES TERMINATED BY char: 行分隔符。
  • SERDE: 指定自定义的序列化/反序列化器。
  • STORED AS: 指定存储文件类型,如 TEXTFILE, SEQUENCEFILE, ORC, PARQUET
  • LOCATION: 指定表在 HDFS 上的存储位置。

建表示例(复杂版)

CREATE TABLE IF NOT EXISTS ds_hive.ch4_user_demo1( 
id INT COMMENT '用户id',
name STRING COMMENT '姓名',
age INT COMMENT '年龄',
subordinates ARRAY<STRING> COMMENT '下属',
deductions MAP<STRING, FLOAT> COMMENT '税务种类',
address STRUCT<street:STRING, city:STRING, state:STRING, zip:INT> COMMENT '地址'
)
COMMENT '用户表'
PARTITIONED BY(data_dt STRING)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
COLLECTION ITEMS TERMINATED BY '|'
MAP KEYS TERMINATED BY ':'
LINES TERMINATED BY '\n'
STORED AS ORC
LOCATION 'hdfs://ds/user/hive/warehouse/ds_hive.db/ch4_user_demo1';

3.2 其他建表方式

(1)CTAS: Create Table As Select

根据 SELECT 查询结果直接创建表,新表会包含查询结果的数据和结构。

CREATE TABLE [IF NOT EXISTS] table_name
[STORED AS file_format]
AS
SELECT ...;

-- 示例
CREATE TABLE ds_hive.ch4_emp2
STORED AS TEXTFILE
AS
SELECT id, name FROM ds_hive.ch4_emp;

(2)Create Table Like

复制一张已存在表的结构,但不复制数据。

CREATE TABLE [IF NOT EXISTS] new_table_name LIKE existing_table_name;

-- 示例
CREATE TABLE ds_hive.ch4_emp3 LIKE ds_hive.ch4_emp;

3.3 修改表 (ALTER TABLE)

(1)重命名表

ALTER TABLE ds_hive.ch4_emp RENAME TO ds_hive.ch4_emp1;

注意:重命名内部表时,其在 HDFS 上的目录也会同步重命名。而重命名外部表时,LOCATION 指向的 HDFS 目录不会改变。

(2)增加/修改/替换列

  • 增加列 (ADD COLUMNS):在现有列之后、分区列之前添加新列。
    ALTER TABLE ds_hive.ch4_emp1 ADD COLUMNS(age INT COMMENT '年龄');
  • 修改列 (CHANGE COLUMN):修改列名、数据类型、注释。

    数据类型只能从小范围转为大范围(如 INT -> DOUBLE),反之不行。

    ALTER TABLE ds_hive.ch4_emp1 CHANGE COLUMN age ages DOUBLE;
  • 替换列 (REPLACE COLUMNS):用一组新列完全替换表原有的所有非分区列。
    ALTER TABLE ds_hive.ch4_emp1 REPLACE COLUMNS (id INT, emp_name STRING);

总结所有 ALTER TABLE 操作修改的都是 Hive 的元数据信息,不会去修改 HDFS 上的原始数据文件。 新增的列在查询旧数据时会显示为 NULL

3.4 删除与清空表

(1)删除表 (DROP TABLE)

DROP TABLE ds_hive.ch4_emp1;
  • 删除内部表:删除元数据和 HDFS 数据。
  • 删除外部表:仅删除元数据。

(2)清空表 (TRUNCATE TABLE)

清空表中的所有数据,但保留表结构。

TRUNCATE TABLE ds_hive.ch4_emp1;

注意TRUNCATE 命令只能用于内部表,执行该操作会删除 HDFS 目录下的数据文件。它不能用于外部表。