行式存储 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 + 树,快速定位单行
- 列存:自带列级排序 / 稀疏索引,聚合查询快,单行点查弱
三、优缺点总结
行式存储
优点
- 适合点查、全字段查询
- 单行 CRUD 性能好,事务支持完善
- 数据写入简单,延迟低
缺点
- 分析类查询只取几列时 IO 巨大
- 压缩率低,存储成本高
- 大规模聚合计算性能差
适用:在线业务数据库 OLTP(MySQL 等)
列式存储
优点
- 统计、聚合、多维分析查询极快
- 同列数据压缩比极高,节省磁盘
- 减少无效 IO,海量数据查询优势明显
缺点
- 读取完整一行需要合并多列,开销大
- 单行实时更新性能差
- 事务支持弱,不适合高频修改业务
适用:数据仓库、大数据分析 OLAP(ClickHouse、StarRocks、Hive)
四、混合存储(现代方案)
- HBase:列簇式,折中方案,同列簇行存,不同列簇分开存储
- Iceberg/Delta Lake:底层 Parquet 列式文件,支持事务更新
- 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 | 聚合后数组内是无名字段组合,上层难解析 |
三、常见踩坑点
- stack 第一个参数必须是数字,传字符串直接报 ParseException
- 错误:
stack('col1', val1, 'col2', val2)正确:stack(2, 'col1', val1, 'col2', val2) - stack 输出列无默认别名,必须用
as (列1,列2)指定别名,否则只能_col0 - named_struct 字段名必须用字符串常量,不能直接写字段名不加引号
- collect_list (stack (...)) 生成的数组无法通过字段名取值,业务聚合优先用 named_struct
四、一句话选型口诀
- 打包多字段、分组聚合存数组 → named_struct
- 一行拆多行、宽表转 key-value 窄表 → stack