列式存储和行式存储;hive中stack和named_struct

行式存储 vs 列式存储

一、核心定义

1. 行式存储(Row Store)

整行连续存放数据,一行所有字段挨在一起。 代表:MySQL、Oracle、SQL Server、PostgreSQL(传统 OLTP 数据库)

示例表:

id name age salary
1 张三 22 8000
2 李四 25 12000

物理存储布局: 1,张三,22,8000 | 2,李四,25,12000

2. 列式存储(Column Store)

同一列连续存放,相同字段放一块。 代表:Hive、ClickHouse、HBase、Vertica、Redshift、StarRocks(OLAP 数仓)

物理存储布局: id:1,2 name:张三,李四 age:22,25 salary:8000,12000

二、关键特性对比

1. 查询场景差异

行存优势:整行读取(OLTP 事务)
  • 业务场景:select * from user where id=100、单条更新、多字段查询
  • 优点:只需加载一行,IO 少,适合高频增删改、在线业务
  • 缺点:只查少数列时,也要读取整行大量无用字段,IO 浪费
列存优势:多表聚合、少量列统计(OLAP 分析)
  • 业务场景:select avg(salary),max(age) from user group by age
  • 优点:只读取需要的几列,跳过无关字段,IO 大幅降低;同类型数据连续,压缩率极高
  • 缺点:取完整一行时,需要拼接多列数据,开销大;单行更新代价高

2. 压缩效率(列存碾压行存)

同一列数据类型相同、重复值多(性别、地区、年份),可使用: RLE、字典编码、Delta、LZ4、ZSTD 等高压缩算法,存储体积常缩小 5~10 倍。 行存每行字段类型杂乱,压缩效果很差。

3. 增删改性能

  • 行存:单行 INSERT/UPDATE/DELETE 很快,直接定位一行修改
  • 列存:写入是批量最优,单行更新低效(需要修改多个列文件),适合批量导入、极少实时更新

4. 索引

  • 行存:主键索引、B + 树,快速定位单行
  • 列存:自带列级排序 / 稀疏索引,聚合查询快,单行点查弱

三、优缺点总结

行式存储

优点

  1. 适合点查、全字段查询
  2. 单行 CRUD 性能好,事务支持完善
  3. 数据写入简单,延迟低

缺点

  1. 分析类查询只取几列时 IO 巨大
  2. 压缩率低,存储成本高
  3. 大规模聚合计算性能差

适用:在线业务数据库 OLTP(MySQL 等)

列式存储

优点

  1. 统计、聚合、多维分析查询极快
  2. 同列数据压缩比极高,节省磁盘
  3. 减少无效 IO,海量数据查询优势明显

缺点

  1. 读取完整一行需要合并多列,开销大
  2. 单行实时更新性能差
  3. 事务支持弱,不适合高频修改业务

适用:数据仓库、大数据分析 OLAP(ClickHouse、StarRocks、Hive)

四、混合存储(现代方案)

  1. HBase:列簇式,折中方案,同列簇行存,不同列簇分开存储
  2. Iceberg/Delta Lake:底层 Parquet 列式文件,支持事务更新
  3. TiDB、OceanBase:支持行存 + 列存双引擎,HTAP 混合负载

五、一句话区分

  • 行存:看人,一整个人打包给你(业务交易)
  • 列存:统计年龄 / 工资,只把所有人的年龄拿出来算报表(数据分析)

Hive中stack vs named_struct

一、核心定义与作用

1. named_struct(构造结构体)

作用 :把同一行的多个字段 打包成一个带字段名的结构化对象,不产生多行,仅列重组。

复制代码
语法:
named_struct(字段名1, 值1, 字段名2, 值2, ...)

示例:
select named_struct('id', 101, 'name', '红色') as item

输出结构: struct<id:int,name:string> 取值方式:shturl.item.name

2. stack(行拆分 / 多行生成)

作用 :将单行数据拆成 N 行 (行转列),第一个参数是拆分后的列数,后续成对传「列名常量、字段值」。

复制代码
语法:
stack(列数N, 列1名, 值1, 列2名, 值2, ...)

示例:
select stack(2, 'attr_id', 101, 'attr_val', '红色')

二、关键区别对照表

维度 named_struct stack
行数量 输入 1 行,输出仍 1 行(只打包列) 输入 1 行,输出多行(行裂变)
输出类型 struct <命名字段> 多个匿名列,无固定结构体名称
核心场景 多行聚合打包(collect_list + struct)、嵌套结构体 行转列、宽表转窄表、多字段平铺成多行
取值方式 . 字段名,可读性强 只能用下标 _col0_col1,无别名
语法风险 无数字参数,不易写错 首参数必须是数字(列数),写错直接报语法错
聚合搭配 完美配合 collect_list/collect_set 聚合后数组内是无名字段组合,上层难解析

三、常见踩坑点

  1. stack 第一个参数必须是数字,传字符串直接报 ParseException
  2. 错误:stack('col1', val1, 'col2', val2) 正确:stack(2, 'col1', val1, 'col2', val2)
  3. stack 输出列无默认别名,必须用 as (列1,列2) 指定别名,否则只能 _col0
  4. named_struct 字段名必须用字符串常量,不能直接写字段名不加引号
  5. collect_list (stack (...)) 生成的数组无法通过字段名取值,业务聚合优先用 named_struct

四、一句话选型口诀

  • 打包多字段、分组聚合存数组 → named_struct
  • 一行拆多行、宽表转 key-value 窄表 → stack