【五】Hive 存储格式与压缩

深入讲解行存与列存原理、Hive 主流存储格式(TextFile / SequenceFile / RCFile / ORC / Parquet / Avro)以及 Hadoop / Hive 中各种压缩算法的选型和参数配置

次阅读

一、行存储、列存储

假设有一张订单表 orders

order_iduser_idamountcitystatus
1001U0199.5BeijingPAID
1002U0250.0ShanghaiPAID
1003U03200.0BeijingUNPAID

1.1 存储和读取区别

🟦 行存储(Row-Based):一整行的字段紧挨着,读到一行就能拿到所有列。(当满足条件需要选取一整行数据时,行查询最快)

[1001, U01, 99.5,  Beijing,  PAID  ] ← 第1行整体连续存放
[1002, U02, 50.0,  Shanghai, PAID  ] ← 第2行整体连续存放
[1003, U03, 200.0, Beijing,  UNPAID] ← 第3行整体连续存放
1
SELECT amount FROM orders WHERE city = 'Beijing'
1
2
 Block  拿到整行(5 )  丢掉 4   只用 amount  city
 Block  拿到整行(5 )  丢掉 4   只用 amount  city

问题:你只要 2 列,但磁盘把 5 列全读进来了,80% 的 IO 是浪费

🟩 列存储(Columnar):同一列的所有值紧挨着,不同列分块存放(甚至不同文件)。(当满足条件需要选取指定少数列数据时,列查询最快)

order_id 列: [1001, 1002, 1003]            ← 同一列连续存放
user_id  列: [U01,  U02,  U03 ]
amount   列: [99.5, 50.0, 200.0]
city     列: [Beijing, Shanghai, Beijing]
status   列: [PAID, PAID, UNPAID]
1
SELECT amount FROM orders WHERE city = 'Beijing'
1
2
只读 city   过滤出 Beijing 对应的行号 [0, 2]
只读 amount 列对应位置  拿到 [99.5, 200.0]

优势:要几列读几列,IO 直接降到行存的 2/5。表越宽(列越多),收益越大。

1.2 为什么列存还能"压缩率超高"?

因为同一列的数据类型相同、取值相近,压缩算法效率惊人。指的就是ORC

行存放一起的数据列存放一起的数据压缩效果
status1001,U01,99.5,Beijing,PAID,1002,U02,...PAID,PAID,PAID,PAID,UNPAID,PAID...列存可用 RLE(Run-length encoding 游程编码)(PAID×4, UNPAID×1, PAID×1),压缩到几个字节
city类型混杂Beijing,Shanghai,Beijing,Beijing...字典编码{0:Beijing, 1:Shanghai} + [0,1,0,0...],整数比字符串小得多
amount类型混杂99.5, 50.0, 200.0, 100.5...位压缩 / Delta 编码:数值范围小,能用少 bit 数表示

实测数据:同一份数据,TextFile 行存 100GB → ORC/Parquet 列存常常压缩到 10-20GB,5-10 倍的压缩比是常态。

1.3 列存的"杀手锏":向量化 + 谓词下推

① 向量化执行(Vectorized Execution)

  • 列存中同列数据类型一致,可以一次性加载一批(如 1024 个 int)到 CPU SIMD 寄存器,单条指令处理多个数据。
  • 行存做不到,因为一行里有 int/string/decimal 混在一起,CPU 没法批量处理。
1
2
行存:for (row in rows) { process(row.amount) }  ← 一次处理 1 个
列存:SIMD_ADD(amount[0..1024])                  ← 一次处理 1024 个

② 谓词下推(Predicate Pushdown, PPD)

列存文件在每个列块里预先存了 min/max/Bloom Filter,可以在不读数据的情况下跳过整块。

1
2
3
4
查询:WHERE amount > 1000
列存元数据:Block1 [min=10, max=500]   → 直接跳过!
          Block2 [min=800, max=2000] → 需要读
          Block3 [min=50, max=300]   → 直接跳过!

3 个块只读 1 个,IO 再降 67%。


1.4 那行存就一无是处了吗?

完全不是。行存有它无可替代的场景:

① 行存的优势场景

  • OLTP 事务处理INSERT INTO ... VALUES (...) 一次写入一整行,行存只需写一个连续区域;列存要拆成 N 列分别写到 N 个位置。
  • 按主键查整行SELECT * FROM orders WHERE id=1001,行存一次 IO 拿到整行;列存要去 N 个列块拼回来。
  • 频繁更新单条记录:行存原地改即可;列存要改 N 个列文件。

② 列存的劣势

  • 写入慢:一行数据要拆开写到多个地方,且通常要批量积攒后落盘。

  • 不适合点查:查一整行要重新"拼装"。

  • 更新困难:通常只支持追加(Append-Only),更新要靠 Delta 文件 + Compaction(这就是 Delta/Iceberg/Hudi 在解决的问题)。

    模式类似于HDFS中的edits/fsimage/Checkpoint:

    • Delta 文件(或日志文件):记录对表数据的所有增量更改(增、删、改)。每次事务提交都会生成新的增量文件。类比edits文件

    • 基础文件(如Parquet/ORC文件):存储表数据在某个时间点的完整列式存储快照。类比fsimage文件

    • Compaction:将一段时间内积累的 Delta 文件合并到基础文件中,生成新的、更紧凑的基础文件,并清理旧的增量文件。这正是为了避免 Delta 文件无限增长,保证查询性能。类比Checkpoint


1.5 行存 vs 列存 对比总表

维度行存储列存储
物理布局一行所有字段连续存放一列所有值连续存放
读取单位整行单列
列裁剪❌ 必须读整行✅ 只读需要的列
压缩率低(数据异构)高(数据同构,5-10×)
谓词下推强(列级 min/max/Bloom)
向量化执行天然支持 SIMD
写入性能✅ 快(一次写一行)❌ 慢(拆成 N 列写)
更新性能✅ 原地更新❌ 通常 Append + Compaction
点查(按主键)✅ 快❌ 需拼装多列
范围扫描 + 聚合❌ 全列读取浪费 IO✅ 极快
典型代表MySQL/Oracle/PostgreSQL(堆表)、TextFile、SequenceFile、AvroORC、Parquet、ClickHouse、Doris、StarRocks、HBase(列族级伪列存)
适用场景OLTP、高频写入、点查、整行读取OLAP、分析查询、大宽表、聚合统计

1.6 混合模式:PAX(行列混合)

现实中纯行/纯列都有缺陷,ORC 和 Parquet 实际上用的是 PAX(Partition Attributes Across)——折中方案:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
┌─────────────────────────────────┐
│  Row Group / Stripe(一组行)     │ ← 先按行切大块(便于并行/分割)
│  ├─ Column Chunk: order_id      │ ← 块内再按列存放
│  ├─ Column Chunk: user_id       │
│  ├─ Column Chunk: amount        │
│  └─ Column Chunk: city          │
└─────────────────────────────────┘

[行组1]
    - 列块1: 用户ID数据: [1, 2, 3, 4, 5, ... 100万]
    - 列块2: 用户名数据: ["Alice", "Bob", "Charlie", ...]
    - 列块3: 行为数据: ["login", "click", "login", ...]
    - 列块4: 时间戳数据: [100, 101, 102, ...]
  • 块间:按行切分(每个 Stripe/RowGroup 包含若干行),方便 HDFS 分块并行处理。
  • 块内:按列存放,享受列存的所有优势(压缩、向量化、PPD)。

这就是为什么 ORC/Parquet 能兼具列存性能 + HDFS 分布式可分割性

二、Hive 存储格式详解

Hive 的存储格式决定了数据在 HDFS 上以什么物理形式落盘,直接影响查询性能、压缩率、IO 开销和计算引擎兼容性

一个简单的协作流程:你的SQL -> Hive(翻译成MapReduce作业) -> MapReduce框架调用InputFormat读取数据、调用SerDe解析成行对象 -> 执行计算 -> 调用SerDe序列化、调用OutputFormat写入数据

在Hive 本身只是"翻译器",真正读写文件的是底层的 InputFormat/OutputFormat 和 SerDe(序列化/反序列化器)。

存储格式演进路线:早期:TextFile → SequenceFile/RCFile → 列存储时代:ORC/Parquet → 数据湖格式:Delta/ICE/Hudi


2.1 Hive 支持的主流存储格式总览

格式类型是否列式压缩支持可分割典型场景
TextFile行式(默认)支持(Gzip/Bzip2/LZO)部分支持原始日志、ODS 落地、跨系统交换
SequenceFile行式(K-V 二进制)支持Hadoop 内部中间数据
RCFile行列混合(已淘汰)✅(早期)支持历史遗留
ORC列式(Hive 原生)内置(Zlib/Snappy/LZ4/Zstd)Hive 数仓首选(DWD/DWS/ADS)
Parquet列式(跨生态)内置(Snappy/Gzip/Zstd)Spark/Impala/Presto 通用,湖仓首选
Avro行式(Schema 演化友好)支持Kafka→Hive 入湖、Schema 频繁变更
JSON/CSV行式(文本)支持部分调试、外部数据导入

2.2 逐个格式详解

① TextFile(默认行式)

  • 本质:纯文本文件,Hive默认格式

  • 存储结构:行存储,每行一条记录,列之间用分隔符(默认 \001

    1,Alice,login,2023-10-01
    2,Bob,click,2023-10-01
    3,Charlie,login,2023-10-02
    
  • 常见类型:CSV、JSON、TSV、日志文件

  • 建表示例

    1
    2
    3
    4
    5
    6
    7
    8
    
    CREATE TABLE logs_text (
        id INT,
        name STRING,
        action STRING,
        log_date Date
    ) ROW FORMAT DELIMITED 
      FIELDS TERMINATED BY ' '
      STORED AS TEXTFILE;
    

    数据存入:当MapReduce的Mapper开始读取第一行1,Alice,login,2023-10-01时,TextInputFormat只是简单地将它作为一个Text对象(可理解为Java的String)读出。这里几乎没有计算开销,所以“无序列化开销”是优点。

    数据读取:此时数据在内存里只是一串文本1,Alice,login,2023-10-01。Hive不知道这串文本的各个部分是什么。这时,SerDe(默认是LazySimpleSerDe)出场了。它需要根据Hive元数据(你在建表时定义的(id INT, name STRING, action STRING, log_date DATE))来“解释”这行文本。SerDe会按照你指定的分隔符(比如,)将这行文本拆分成字段数组["1", "Alice", "login", "2023-10-01"],然后逐个字段地尝试转换为目标类型(String转成INT,String转成DATE)。这个“转换”过程就是反序列化,它发生在数据读取时,而不是文件本身存储了类型信息。

    所以,“不存schema”导致每次查询都要做一次“文本解析+类型转换”,这是性能损耗的来源之一。

  • 优点

    • 人类可读,易于调试
    • 跨平台,兼容性极佳,几乎所有工具都支持,易导入导出
    • 无序列化/反序列化开销
  • 缺点

    • 无压缩,存储效率最低

      • 行存储,同一行存储的数据类型不同,无法进行数据压缩
    • 不存 schema,类型靠 Hive 元数据"解释"

      • 需要按照定义的元数据类型,将文本转为元数据类型
    • 不支持复杂类型

    • 查询性能最差,要全表扫描。

      • 这是由TextInputFormat的“行定位”方式决定的。它无法像ORC/Parquet那样,在文件的“头部”或“尾部”找到数据的统计信息(如最大值、最小值)或索引。

      • 当你执行SELECT * FROM user_log WHERE id = 100时,优化器(如果有的话)发现TextFile没有任何可以用来“跳过”部分数据的信息。InputFormat只能老老实实地从文件第一字节开始,逐行读取、切分、然后由SerDe转换成行对象,再判断id是否等于100。即使你要找的数据在最后一行,也必须读完前面所有行。这就是“全表扫描”。

    • 可结合Gzip、Bzip2使用(系统自动价差,执行查询时自动解压),但使用这种方式,hive不会对数据进行切分,从而无法对数据进行并行操作。会导致 MapReduce 只能单 Mapper 处理整个文件。

      • 正常情况(未压缩):假设user_log.txt有1GB,HDFS默认块大小128MB,那么这个文件会被切分成约8个块,存储在不同的DataNode上。TextInputFormat可以为每个块启动一个Mapper来并行处理。因为文本文件很容易从任意位置开始找到一行的起始点(向后扫描直到遇到换行符即可)。

      • Gzip压缩后:Gzip/Bzip2是整体压缩算法。为了解压1,Alice,...这段数据,可能需要依赖前面几十KB的压缩上下文。这意味着:

        无法从任意点开始解压:你无法从第128MB的位置(第二个块的起始点)开始解压,因为你没有之前的压缩字典。

        • 只能单线程:因此,TextInputFormat在遇到一个Gzip压缩的.txt文件时,不会把它切成多个Split,而是将整个文件作为一个Split,交给**一个Mapper**​ 处理。这就完全丧失了MapReduce的并行能力,性能瓶颈极大。
  • 使用场景

    • ODS 原始落地层
    • 数据交换格式
    • 小规模测试数据

② SequenceFile(行式二进制)

  • 本质:Hadoop键值对序列化格式

  • 存储结构:行存储,二进制格式

  • 文件结构

    1
    2
    3
    4
    
    Header (版本、Key/Value类名、压缩信息)
    Record 1: [Key长度][Value长度][Key][Value]
    Record 2: ...
    Sync Marker (用于切分)
    
  • 三种压缩模式

    1
    2
    3
    4
    5
    6
    
    // 1. 不压缩
    SequenceFile.createWriter(..., CompressionType.NONE);
    // 2. 记录压缩(只压缩Value)
    SequenceFile.createWriter(..., CompressionType.RECORD);
    // 3. 块压缩(多个记录一起压缩)
    SequenceFile.createWriter(..., CompressionType.BLOCK);
    
  • Hive中使用

    1
    2
    3
    4
    
    CREATE TABLE seq_table (
        id INT,
        data STRING
    ) STORED AS SEQUENCEFILE;
    
  • 优点

    • 比 TextFile 高效,可分割,适合MapReduce
    • 支持压缩(块压缩效率高)
    • 支持二进制数据
  • 缺点

    • 仍是行式,非列存储,分析性能有限
    • 非Hive专属,优化有限
    • 可读性差
  • 使用场景:MapReduce中间数据存储

  • 现状:已逐渐被 ORC/Parquet 替代,更多用于 MR 中间结果。

③ RCFile (Record Columnar File) - 早期的列存储尝试

  • 设计思想:先按行分块,块内按列存储

  • 物理结构

    1
    2
    3
    4
    5
    6
    7
    8
    
    Row Group 1:
      Sync Marker
      Metadata Header
      Column 1 Data (压缩)
      Column 2 Data (压缩)
      Column 3 Data (压缩)
    Row Group 2:
      ...
    
  • 特点

    • 混合行列存储:大方向是行分块,块内是列存储

    • 轻量级索引:每个行组的元数据

  • 示例

    1
    2
    3
    4
    5
    
    CREATE TABLE rc_table (
        user_id INT,
        name STRING,
        age INT
    ) STORED AS RCFILE;
    
  • 现状:已被ORC取代,基本淘汰


④ ORC(Optimized Row Columnar)⭐ Hive 首选

Hive 阵营的"亲儿子",由 Hortonworks 在hive 0.11版本提出,为Hive优化的极致列存储

核心特性:列存储 + 轻量级索引 + 复杂类型支持 + ACID事务

文件结构

每个ORC文件由一个或多个stripe组成,每个scripe大小250MB,stripe相当于RowGroup概念

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
┌──────────────────────────────────────────────┐
│  File Header(魔数 "ORC",3 字节)            │
├──────────────────────────────────────────────┤
│  Stripe 1(默认 ~250MB,是并行处理的基本单位) │
│  ├─ Index Data                               │
│  │   ├─ 每列的 min/max/sum/count(实现谓词下推,跳过不满足条件的数据块)             │
│  │   ├─ Row Group 索引(每 10000 行一组)     │
│  │   └─ Bloom Filter(可选,针对等值过滤)    │
│  ├─ Row Data(列式存放的实际数据) 
│  │   ├─ 实际数据,先取部分行,对行进行按列存储           │
│  │   ├─ Column 1: Stream A、Stream B...      │
│  │   ├─ Column 2: Stream A、Stream B...      │
│  │   └─ ...                                  │
│  └─ Stripe Footer(各列的编码方式、位置)     │
├──────────────────────────────────────────────┤
│  Stripe 2 ...                                │
├──────────────────────────────────────────────┤
│  Stripe N ...                                │
├──────────────────────────────────────────────┤
│  File Footer(schema、行组(Stripe)列表、各列统计信息(每个col的数据类型等)  │
├──────────────────────────────────────────────┤
│  Postscript(压缩类型、Footer 长度、版本)    │
└──────────────────────────────────────────────┘

在读取文件时,会seek到文件尾部读PostScript,从里面解析到File Footer长度,再读FileFooter,
从里面解析到各个Stripe信息,再读各个Stripe,即从后往前读。

核心优化技术

  1. 三层索引体系

    层级粒度作用
    File 级整个文件每列的 min/max/count,跳过整个文件
    Stripe 级~250MB每个 Stripe 内每列的 min/max,跳过整个 Stripe
    Row Group 级10000 行每个 Row Group 内每列的 min/max,跳过 Row Group
    Bloom Filter可配置等值查询时快速判断"一定不存在"

    实现谓词下推,跳过不满足条件的数据块

  2. 高效编码

    1
    2
    3
    4
    
    // ORC支持的编码
    - Integer Column Encoding (RLE, Delta, Zigzag)
    - String Dictionary Encoding (字典编码)
    - Bit Packing (位打包)
    
  3. 复杂类型支持

    1
    2
    3
    4
    5
    6
    7
    
    -- 支持struct, array, map, union
    CREATE TABLE nested_orc (
       id INT,
       user STRUCT<name:STRING, age:INT>,
       tags ARRAY<STRING>,
       scores MAP<STRING, INT>
    ) STORED AS ORC;
    

4- Stream 概念

ORC 把一列拆成多个 Stream(流) 物理存储:

  • PRESENT Stream:位图,标记每行是否为 NULL。

  • DATA Stream:实际数据。

  • LENGTH Stream:变长字段(如 STRING)的长度。

  • DICTIONARY_DATA Stream:字典编码时的字典内容。

  • SECONDARY Stream:辅助流(如 Decimal 的小数部分)。

    好处:NULL 值不占数据空间,字典和数据分离便于压缩。

ORC性能调优参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
CREATE TABLE optimized_orc (
    id BIGINT,
    data STRING
) STORED AS ORC
TBLPROPERTIES (
    "orc.compress"="SNAPPY",           -- 压缩算法:NONE, ZLIB, SNAPPY
    "orc.compress.size"="262144",      -- 压缩块大小
    "orc.stripe.size"="67108864",      -- Stripe大小,默认64MB
    "orc.row.index.stride"="10000",    -- 索引粒度
    "orc.create.index"="true",         -- 创建索引
    "orc.bloom.filter.columns"="id",   -- 布隆过滤,加速等值查询
    "orc.bloom.filter.fpp"="0.05"      -- 误判率
);

ORC ACID事务支持(Hive 3+)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
-- 1. 启用ACID
SET hive.support.concurrency=true;
SET hive.txn.manager=org.apache.hadoop.hive.ql.lockmgr.DbTxnManager;

-- 2. 创建事务表
CREATE TABLE acid_table (
    id INT,
    value STRING
) STORED AS ORC
TBLPROPERTIES ('transactional'='true');

-- 3. 支持更新/删除/合并
UPDATE acid_table SET value='new' WHERE id=1;
DELETE FROM acid_table WHERE id=2;
MERGE INTO acid_table AS target ...

核心优势

  • 列式存储:只读需要的列,IO 大幅降低。
  • 三级索引:File / Stripe / Row Group(每 1 万行一个 index),支持谓词下推(PPD),可在不读数据的情况下用 min/max 跳过整段。
  • 轻量编码:RLE(游程编码)、字典编码、Bit Packing。
  • ACID 支持:Hive 3.x 事务表必须用 ORC(INSERT/UPDATE/DELETE)。
  • 向量化执行:配合 hive.vectorized.execution.enabled=true,按 1024 行一批处理,性能翻倍。

⑤ Parquet(列式,跨生态)⭐ 湖仓首选

由 Twitter + Cloudera 2015-5月联合提出,Spark/Impala/Presto/Flink 原生支持,是 Delta/Iceberg/Hudi 的默认底层格式。

Parquet文件是以二进制方法存储,不可直接读取,文件中包括文件的数据和元数据,所以Parquet格式是自解析的。

存储Parquet数据时会按照Block大小设置行组的大小,一个mapper任务处理的最小单位是block,所以一行由一个mapper处理,增大任务并行度。

Parquet 文件结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
+---------------------------+
|     Magic Number "PAR1"   |  # 4字节,文件起始标识(校验是否是一个Parquet文件)
+---------------------------+
|                           |
|     Row Group 1           |  # 第一个行组 (默认 128MB,对齐 HDFS Block) 
|     +-----------------+   |
|     | Column Chunk 1  |   |  # id列 (一整列在该 Row Group 的数据)
|     |   Page 1        |   |
|     |   Page 2        |   |
|     +-----------------+   |
|     | Column Chunk 2  |   |  # name列
|     |   Page 1        |   |
|     +-----------------+   |
|     | ...             |   |
|                           |
|     Row Group 2           |  # 第二个行组
|     ...                   |
|                           |
+---------------------------+
|     File MetaData         |  # 文件元数据
|     +-----------------+   |
|     | Schema          |   |  # 数据结构
|     | Row Group 1 Meta|   |  # 行组1统计信息
|     | Row Group 2 Meta|   |  # 行组2统计信息
|     | Column Metadata |   |  # 列统计信息
|     +-----------------+   |
+---------------------------+
| MetaData/Footer Length    |  # 4字节,元数据长度
+---------------------------+
|     Magic Number "PAR1"   |  # 4字节,文件结束标识
+---------------------------+

Dremel 嵌套模型(Parquet 的"独门绝技")

Parquet 通过 Repetition Level(重复层级)Definition Level(定义层级) 把任意嵌套结构(Struct/Array/Map)“扁平化"成列存。

示例 schema:
message User {
  required int64 id;
  repeated group address {
    optional string city;
    optional string zip;
  }
}

一个用户可以有多个 address,每个 address 有 city/zip 可空。Parquet 用两个额外的整数列记录"这是第几次重复"和"嵌套到第几层是有值的”,从而精确还原结构。

ORC 也支持嵌套,但实现不如 Parquet 优雅,这也是 Parquet 在 JSON/半结构化数据场景占优的原因。

关键点:Parquet 自身没有事务能力,是靠 Delta/Iceberg/Hudi 在它之上叠加事务层实现的。而 ORC 在 Hive 3.x 是原生事务表的唯一选择。


⑥ Avro(行式,Schema 演化)

  • 行式 + Schema 与数据一起存,对字段增减、类型变更非常友好。
  • 常用于 Kafka → Hive 的入湖链路(配合 Schema Registry)。
  • 分析性能弱于 ORC/Parquet,但灵活性强。

2.3 ORC vs Parquet(重点对比)

维度ORCParquet
生态归属Hive 原生Spark/Impala/Presto 原生
压缩率通常更高(5%-10%)略低,但已很优秀
查询性能(Hive)更快(向量化、PPD 更成熟)也很快
查询性能(Spark)良好更优(Spark 默认)
嵌套类型支持支持,但不如 Parquet 优雅原生 Dremel 模型,更强
ACID 事务✅ Hive 事务表必须 ORC❌ 不支持 Hive 事务
索引File/Stripe/RowGroup 三级 + BloomRowGroup + Page 统计
湖仓兼容Iceberg 支持Delta/Iceberg/Hudi 默认

① 建表对比

ORC 建表(Hive 推荐)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
CREATE TABLE dwd_order_orc (
  order_id   BIGINT,
  user_id    STRING,
  amount     DECIMAL(18,2),
  city       STRING,
  create_time TIMESTAMP
)
PARTITIONED BY (dt STRING)
CLUSTERED BY (user_id) INTO 32 BUCKETS
STORED AS ORC
TBLPROPERTIES (
  "orc.compress"="ZSTD",                    -- 压缩算法
  "orc.compress.size"="262144",             -- 压缩块大小 256KB
  "orc.stripe.size"="268435456",            -- Stripe 大小 256MB
  "orc.row.index.stride"="10000",           -- Row Group 行数
  "orc.create.index"="true",                -- 启用索引
  "orc.bloom.filter.columns"="user_id,city",-- 对高频过滤列建 Bloom
  "orc.bloom.filter.fpp"="0.05"             -- Bloom 误判率
);

Parquet 建表(Spark / 多引擎推荐)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
CREATE TABLE dwd_order_parquet (
  order_id   BIGINT,
  user_id    STRING,
  amount     DECIMAL(18,2),
  city       STRING,
  create_time TIMESTAMP
)
PARTITIONED BY (dt STRING)
STORED AS PARQUET
TBLPROPERTIES (
  "parquet.compression"="SNAPPY",           -- 或 ZSTD
  "parquet.block.size"="134217728",         -- Row Group 大小 128MB
  "parquet.page.size"="1048576",            -- Page 大小 1MB
  "parquet.enable.dictionary"="true",       -- 启用字典编码
  "parquet.bloom.filter.enabled"="true"     -- Bloom(1.12+)
);

② 结构对应关系

ParquetORC含义
FileFile整个文件
Row Group(128MB)Stripe(250MB)并行处理基本单位
Column ChunkColumn(在 Stripe 内)一列在一个并行单元的数据
Page(1MB)Row Group(10000 行)编码压缩 + 索引最小单位
Page Header 统计Row Index行级粒度索引
Column Chunk 统计Stripe-level Statistics块级索引
File MetadataFile Footer文件级元数据

③ 索引与谓词下推(PPD)

ORC 的索引能力(更强)

查询: SELECT * FROM orders 
WHERE amount > 1000 AND city = 'Beijing'

ORC 的过滤路径:

  1. File Footer:看整个文件的 amount.max,如果 < 1000 直接跳过。
  2. Stripe Statistics:逐个 Stripe 看 amount.max,过滤掉。
  3. Row Group Index(每 10000 行):精确到 10000 行粒度过滤。
  4. Bloom Filter:对 city = 'Beijing' 直接判断"该 Row Group 是否可能包含 Beijing"。
  5. 读取数据:上面层层过滤后只剩很少的数据需要真正解码。

Parquet 的索引能力

  • 传统 Parquet:只有 Row Group 级和 Column Chunk 级的统计(min/max/null_count)。
  • Parquet 2.x 新增 Page Index:补齐了 Page 级(≈ ORC 的 Row Group 级)索引。
  • Bloom Filter:Parquet 1.12+ 才支持(晚于 ORC)。

结论:在 Hive 引擎下,ORC 的谓词下推历史更成熟、过滤更精细;但 Parquet 新版本已经追上,且在 Spark/湖仓生态中表现优秀。

④ 编码与压缩技术

1) 编码(Encoding)—— 减少数据本身的体积

编码方式原理适用列ORCParquet
RLE(游程编码)AAAABBC(A,4)(B,2)(C,1)重复值多的列(如 status、gender)
字典编码(Dictionary)用整数 ID 代替字符串,建一张字典低基数字符串(如 city、product_name)
Bit Packing(位压缩)用最少 bit 表示数值(如 0-100 只需 7 bit)小范围整数
Delta 编码存差值(如 时间戳、自增 ID)单调/近单调数值
Run Length + Dictionary 混合字典 + 游程高重复低基数
FOR(Frame of Reference)存"基准值 + 偏移"范围集中的数值✅(变种)

2) 压缩(Compression)—— 在编码基础上再压一层

压缩算法压缩比速度ORC 默认Parquet 默认适用场景
None极快--测试
Snappy2-3×可选✅ 默认平衡型,查询友好
Zlib (Gzip)3-4×中等✅ 默认可选存储成本敏感
LZ4极快可选可选实时查询
Zstd3-5×可选可选新选择,压缩比+速度俱佳
Brotli4-5×可选冷数据归档

实战推荐:现代生产环境优先用 ZSTD,压缩比接近 Zlib,速度接近 Snappy。

三、Hive 压缩格式

核心理解:Hive 不自己实现压缩,它复用 Hadoop 的压缩 Codec 体系。所以 Hive 压缩 = Hadoop 压缩 + MapReduce 压缩参数 + Hive 自己的开关。

3.1 Hive 中的压缩格式(总览)

Hive 中常见的压缩格式有 6 种,决策核心是三个维度:压缩率、压缩/解压速度、是否可切分(splittable)

压缩格式扩展名压缩率压缩速度解压速度是否可切分Hadoop 原生支持
DEFLATE.deflate
Gzip.gz
Bzip2.bz2极高极慢
LZO.lzo✅(需建索引)❌(需额外安装)
Snappy.snappy极快极快❌(需额外安装)
LZ4.lz4极快极快
Zstd.zst✅(新版本)

📌 关键认知

  • 是否可切分 决定了一个大文件能否被多个 Map 并行处理 → 大数据场景下极其重要。
  • Gzip / Snappy 不可切分,所以大文件用 Gzip 会丢掉并行度
  • 解决方式:用 ORC / Parquet 这种自带分块的列式存储 + 内部 Snappy/Zlib 压缩(块级别可切分)。

3.2 MR 支持的压缩格式 & Hadoop 压缩类(Codec)

输入压缩:MR自动识别并解压(通过InputFormat自动处理)

输出压缩:需显式配置

1
2
3
4
5
6
7
8
9
<!-- mapred-site.xml 配置示例 -->
<property>
  <name>mapreduce.output.fileoutputformat.compress</name>
  <value>true</value>
</property>
<property>
  <name>mapreduce.output.fileoutputformat.compress.codec</name>
  <value>org.apache.hadoop.io.compress.SnappyCodec</value>
</property>

MapReduce 通过 CompressionCodec 接口 调用各种压缩算法,每种格式对应一个 Codec 类。

Hadoop 编码/解码器类(必背)

压缩格式对应的 Codec 类
DEFLATEorg.apache.hadoop.io.compress.DefaultCodec
Gziporg.apache.hadoop.io.compress.GzipCodecJava自带,无需Native库
Bzip2org.apache.hadoop.io.compress.BZip2Codec纯Java实现,支持分片
LZOcom.hadoop.compression.lzo.LzopCodec需单独安装,支持索引创建
Snappyorg.apache.hadoop.io.compress.SnappyCodec需Native库(libsnappy.so),性能极佳
LZ4org.apache.hadoop.io.compress.Lz4Codec需Native库,超高速压缩
Zstdorg.apache.hadoop.io.compress.ZStandardCodec

📌 这些类的作用:告诉 MR / Hive 用什么算法去压缩或解压数据

Native库检查命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 检查Hadoop Native库支持
hadoop checknative

# 预期输出示例:
# Native library checking:
# hadoop:  true /opt/hadoop/lib/native/libhadoop.so.1.0.0
# zlib:    true /lib64/libz.so.1
# snappy:  true /usr/lib64/libsnappy.so.1
# lz4:     true /usr/lib64/liblz4.so.1
# bzip2:   false
# openssl: false

3.3 压缩性能比较(经典对比数据)

以一个 ~8.3 GB 文本文件压缩为例(业内常引用的对比):

算法压缩后大小压缩比压缩速度解压速度
Gzip1.8 GB78%17.5 MB/s58 MB/s
Bzip21.1 GB86%2.4 MB/s9.5 MB/s
LZO2.9 GB65%49.3 MB/s74.6 MB/s
Snappy~2.2 GB73%250+ MB/s500+ MB/s

选型口诀(🔥面试高频)

业务场景推荐
高压缩比的存储优化(冷数据/归档)Bzip2 / Gzip
低CPU开销的计算优化(Shuffle 中间数据)Snappy / LZ4
支持分片+较好压缩比的平衡选择(大日志文本)LZO(带索引)/ Bzip2
现代综合最优Zstd

📌 结论

  • I/O 密集型作业 → 加压缩往往提速(少传输 = 快)。
  • CPU 密集型作业 → 压缩反而拖慢(CPU 已满,再压缩没资源)。

3.4 Hadoop 压缩参数(必须熟记)

Hadoop 中压缩可发生在 三处:输入端、Map 输出端、Reduce 输出端。

① 输入端(通常自动识别扩展名解压)

core-site.xml中配置如下(Hadoop通过扩展名判断是否支持某种编解码器)

1
2
3
4
5
6
io.compression.codecs =
  org.apache.hadoop.io.compress.GzipCodec,
  org.apache.hadoop.io.compress.DefaultCodec,
  org.apache.hadoop.io.compress.BZip2Cod
  org.apache.hadoop.io.compress.Lz4Codec,
  org.apache.hadoop.io.compress.SnappyCodec

② Map 输出端压缩参数(Shuffle 优化的关键)

1
2
3
4
-- 默认为false,设置为true启用压缩
mapreduce.map.output.compress = true
--  默认为DefaultCodec,可以使用core-site.xml中设置的其他编码器
mapreduce.map.output.compress.codec = org.apache.hadoop.io.compress.SnappyCodec

③ Reduce 输出端(最终落盘)压缩参数

1
2
3
4
5
6
-- 默认为false,设置为true启用压缩
mapreduce.output.fileoutputformat.compress = true
-- 默认为DefaultCodec,可以使用core-site.xml中设置的其他解码器
mapreduce.output.fileoutputformat.compress.codec = org.apache.hadoop.io.compress.GzipCodec
-- 输出使用的压缩类型,默认为RECORD
mapreduce.output.fileoutputformat.compress.type = BLOCK   # 推荐 BLOCK,比 RECORD 压缩率高

📌 type 参数

  • NONE:不压缩
  • RECORD:按记录压缩(压缩率低)
  • BLOCK:按块压缩(推荐,压缩率高,仅作用于 SequenceFile)

3.5 Hive 在 Map 端 / Reduce 端的压缩方式

Hive 在 Hadoop 参数上又封装了两个自己的开关必须先打开 Hive 的开关,再设置 Hadoop 参数才生效

① Map 端压缩(中间结果,影响 Shuffle)

1
2
3
4
5
6
7
8
-- 启用Map输出压缩(推荐Snappy/LZ4)
SET hive.exec.compress.intermediate=true; -- 总开关
SET hive.intermediate.compression.codec=org.apache.hadoop.io.compress.SnappyCodec;
SET hive.intermediate.compression.type=BLOCK;

-- 底层对应对应MR参数
SET mapreduce.map.output.compress=true;
SET mapreduce.map.output.compress.codec=org.apache.hadoop.io.compress.SnappyCodec;

👉 目的:减少 Map → Reduce 之间的网络传输和磁盘 IO。
👉 推荐Snappy / LZ4(压缩快、解压快,CPU 开销低)。


② Reduce 端压缩(最终输出到 HDFS)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
-- ① Hive 总开关
set hive.exec.compress.output = true;

-- ② 底层 Hadoop 参数
-- 开启mapreduce最终输出数据压缩
set mapreduce.output.fileoutputformat.compress = true;
-- 开启mapreduce最终输出数据压缩
set mapreduce.output.fileoutputformat.compress.codec = org.apache.hadoop.io.compress.GzipCodec;
-- 开启mapreduce最终输出数据压缩为块压缩
set mapreduce.output.fileoutputformat.compress.type = BLOCK;

👉 目的:节省 HDFS 存储空间。
👉 推荐Gzip / Zstd / Bzip2(压缩率优先)。


③ Hive 压缩流程图(建议背下来)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
+---------------------------+   +---------------------------+   +---------------------------+   +---------------------------+
|        输入文件            |   |        自动解压             |   |        Map处理            |   |      Map输出压缩            |
|                           |-->|                           |-->|                           |-->|                           |
|  [data.gz/.snappy/...]    |   |  (自动识别压缩格式)          |   |  (执行Map任务逻辑)          |   |  (hive.exec.compress.     |
|                           |   |                           |   |                           |   |  intermediate控制)         |
+---------------------------+   +---------------------------+   +---------------------------+   +---------------------------+
                                                                         
                                                             +---------------------------+
                                                             |      中间压缩配置           |
                                                             |                           |
                                                             | hive.exec.compress.       |
                                                             | intermediate=true         |
                                                             |                           |
                                                             | 常用codec:                 |
                                                             |  Snappy (速度快)          |
                                                             |  LZ4 (快速)               |
                                                             +---------------------------+

+---------------------------+   +---------------------------+   +---------------------------+   +-------------------------+
|     网络传输(Shuffle)       |   |        解压               |   |      Reduce处理            |   |      输出压缩            |
|                           |-->|                           |-->|                           |-->|                         |
|  (压缩数据通过网卡传输)       |   |  (准备Reduce处理)          |   |  (执行Reduce任务逻辑)       |   |  (hive.exec.compress.   |
|   ↓网络带宽优化              |   |                          |   |                           |   |  output控制)             |
+---------------------------+   +---------------------------+   +---------------------------+   +-------------------------+
                                                                                                             
                                                             +---------------------------+   +--------------------------+
                                                             |      最终输出配置           |   |      写入HDFS             |
                                                             |                           |   |                          |
                                                             | hive.exec.compress.       |-->|                          |
                                                             | output=true               |   |  (压缩存储,节省空间)       |
                                                             |                           |   |                          |
                                                             | 常用codec:                 |   +-------------------------+
                                                             |  Gzip (压缩率高)          |   | 常用存储格式:              |
                                                             |  Zstandard (平衡性好)     |   |  ORC+ZSTD               |
                                                             +---------------------------+   | • Parquet+SNAPPY         |
                                                                                             +--------------------------+

3.6 与 Hive 存储格式(ORC / Parquet)的配合

Hive 真实生产中,压缩往往和列式存储格式绑定使用

存储格式默认压缩可选压缩
TextFileGzip / Bzip2 / Snappy
SequenceFileRECORDRECORD / BLOCK
ORCZlibZlib / Snappy / LZO / Zstd
ParquetSnappySnappy / Gzip / LZO / Zstd

📌 建表时指定压缩示例

1
2
3
4
5
6
7
8
-- ORC + Snappy
CREATE TABLE t_orc (id INT, name STRING)
STORED AS ORC
TBLPROPERTIES ("orc.compress" = "SNAPPY");
-- Parquet + Gzip
CREATE TABLE t_parquet (id INT, name STRING)
STORED AS PARQUET
TBLPROPERTIES ("parquet.compression" = "GZIP");

📌 重要认知

  • ORC / Parquet 天然按块压缩 → 即使用 Snappy / Gzip 这种不可切分算法,仍然可以并行处理(因为切分发生在 stripe / row group 级别)。
  • 所以列式存储 + Snappy 是当前数仓的事实标准组合
使用 Hugo 构建
主题 StackJimmy 设计
无法复制,本站文章内容受保护