4、数据存储选型:OLAP与OLTP的区别、列式存储的优势、HDFS与对象存储的对比

这一章,我们来聊聊数据仓库的「地基」——存储选型。

说实话,很多刚入行的朋友容易在这上面栽跟头。我见过一个团队,把交易库的MySQL直接拿来跑分析报表,结果每次跑月报都要锁表,业务方直接炸毛。也见过有人把几百GB的Parquet文件扔到普通文件服务器上,查询慢得像蜗牛爬。

存储选型选错了,后面再怎么优化都白搭。今天我就把这块掰开了讲清楚。

4.1 OLAP与OLTP:两个世界的差异

先问个问题:你平时用的MySQL、PostgreSQL,和Hive、ClickHouse,到底有啥本质区别?

说白了,它们是为完全不同的场景设计的。

OLTP(在线事务处理):面向用户,处理高频、小批量的增删改查。典型代表:MySQL、Oracle、PostgreSQL。

OLAP(在线分析处理):面向分析师,处理低频、大批量的复杂查询。典型代表:ClickHouse、Druid、Hive。

我举个例子你就明白了。你去超市买东西,收银员扫一下商品条码,扣库存、记账、打印小票——这就是OLTP。它要求快,毫秒级响应,而且不能出错。而超市经理到了月底,想看「过去三个月哪些商品卖得最好」,这就是OLAP。它要扫描海量数据,做聚合、排序、分组,几秒甚至几分钟出结果都行。

两者的核心差异,我整理了一张表:

对比维度 OLTP OLAP
典型场景 订单、支付、用户注册 报表、BI分析、数据挖掘
读写模式 频繁写入,少量读取 批量写入,大量读取
每次操作数据量 几十行 百万到亿级行
响应时间要求 毫秒级 秒级到分钟级
数据一致性 强一致性(ACID) 最终一致性即可
存储格式 行式存储 列式存储

避坑指南:我曾经见过一个项目,直接用MySQL跑几十亿行的聚合查询,结果每次查询都要十几分钟,还把主库拖垮了。记住:不要用OLTP的数据库做OLAP的事情,反之亦然。

4.2 列式存储:Parquet与ORC的优势

好,既然OLAP要用列式存储,那它到底好在哪?

你想想看,行式存储就像一本流水账,每一行记录了一个完整的事件。你要查「所有人的年龄总和」,就得把整本书从头翻到尾,每一行都读一遍。而列式存储呢?它把「年龄」这一列单独抽出来,像一叠卡片。你只需要翻这一叠卡片就行了,其他列根本不用碰。

这就是列式存储的核心优势:按需读取

在数据仓库领域,最主流的两种列式存储格式是Parquet和ORC。我两个都用过,说说我的感受。

Parquet

  • 由Twitter和Cloudera联合开发,现在是Apache顶级项目
  • 支持嵌套数据结构(比如JSON、Protocol Buffers)
  • 压缩率极高,我实测过,同样的数据比文本格式小70%-80%
  • 与Spark、Hive、Impala等生态集成最好

ORC

  • 由Hortonworks(现Cloudera)开发,专为Hive优化
  • 内置了轻量级的索引(如Bloom Filter、Min/Max索引)
  • 在Hive上的查询性能通常比Parquet快20%-30%
  • 但跨生态的兼容性不如Parquet

我的建议:如果你的技术栈以Spark为主,选Parquet准没错。如果主要用Hive,ORC可能更香。我个人习惯在数据湖里统一用Parquet,因为它的生态兼容性最好,换引擎时不用改存储格式。

来看一个实际例子。假设我们有1亿条用户数据,每条包含id、name、age、city、phone等20个字段。现在要统计「每个城市的平均年龄」。

如果用行式存储(比如CSV),你需要读取全部20个字段,也就是20亿个字段值。而用列式存储,你只需要读取city和age两列,也就是2亿个字段值。数据扫描量直接减少了90%。

再加上列式存储的高压缩率(同一列的数据类型相同,压缩效果更好),实际IO开销可能只有行式存储的5%-10%。

-- 在Hive中创建ORC格式的表
CREATE TABLE user_behavior (
  user_id STRING,
  event_time TIMESTAMP,
  event_type STRING,
  page_url STRING
)
STORED AS ORC
TBLPROPERTIES (
  'orc.compress' = 'SNAPPY',
  'orc.bloom.filter.columns' = 'event_type'
);

-- 在Spark中写入Parquet
df.write
  .mode("overwrite")
  .option("compression", "snappy")
  .parquet("/data/user_behavior/")

4.3 HDFS与对象存储:数据仓库的两种底座

存储格式选好了,那数据放哪?

传统方案是HDFS,云原生方案是对象存储(如AWS S3、阿里云OSS、MinIO)。这两者怎么选?

先看一张对比图:

对比维度 HDFS 对象存储
架构 主从架构(NameNode + DataNode) 分布式、无中心节点
扩展性 受限于NameNode内存,一般几千节点 近乎无限扩展
数据一致性 强一致性(追加写) 最终一致性(部分场景)
适用场景 本地部署、大数据批处理 云原生、数据湖、弹性伸缩
成本 硬件成本高,运维复杂 按量付费,运维简单
典型产品 Hadoop HDFS S3、OSS、MinIO、Ceph

我在项目中两种都用过。说实话,如果是从零开始的新项目,我建议优先考虑对象存储。原因有三:

  1. 运维成本低:HDFS需要专门的人维护NameNode的高可用、磁盘均衡、数据副本管理。对象存储这些都不用操心。
  2. 弹性伸缩:业务量大了,对象存储自动扩展。HDFS你得提前规划节点数,加节点还要做数据重平衡。
  3. 生态兼容:现在Spark、Flink、Trino都原生支持S3协议,用起来和HDFS几乎没区别。

注意:对象存储有一个坑——最终一致性。比如你刚写入一个文件,立刻去读,可能读不到。这在批处理场景下问题不大(因为通常写完后才触发下游任务),但在实时场景下要小心。我曾经遇到过,用Flink实时写入S3,下游Spark任务立刻读取,结果丢了一部分数据。解决方案是加一个「文件可见性等待」机制,或者用支持强一致性的对象存储(如MinIO的纠删码模式)。

下面这张图,是我总结的数据存储选型决策流程:

数据存储选型决策流程 数据存储选型 场景:在线交易 vs 分析查询? OLTP OLAP 行式存储(MySQL/PostgreSQL) 列式存储(Parquet/ORC) 部署环境:本地 vs 云? 本地部署 云原生 HDFS 对象存储(S3/OSS)

嗯,这里要注意一点:HDFS和对象存储并不是非此即彼的关系。很多大厂的做法是「分层存储」——热数据放HDFS(追求低延迟),冷数据放对象存储(追求低成本)。比如我们之前做过一个项目,最近7天的数据在HDFS上,7天前的数据自动归档到S3,查询时透明访问。这样既保证了性能,又控制了成本。

小技巧:如果你用Spark,可以这样配置分层存储:

spark.hadoop.fs.s3a.impl=org.apache.hadoop.fs.s3a.S3AFileSystem
spark.hadoop.fs.s3a.endpoint=https://s3.amazonaws.com
# 热数据路径
val hotPath = "hdfs://namenode:8020/data/hot/"
# 冷数据路径
val coldPath = "s3a://my-bucket/data/cold/"

最后总结一下我的个人经验:

  • OLTP vs OLAP:别混用,各司其职。交易库和分析库一定要分开。
  • 列式存储:Parquet是万金油,ORC在Hive场景下更优。压缩格式推荐Snappy,平衡速度和压缩比。
  • HDFS vs 对象存储:新项目优先对象存储,省心省力。老项目如果已经上了HDFS,可以考虑分层迁移。

存储选型这块,说白了就是「场景决定技术」。没有最好的存储,只有最合适的。你想想看,把MySQL当数仓用,就像拿菜刀砍大树——不是不能砍,但效率太低了。选对了工具,后面的事就顺了。


公众号:蓝海资料掘金营,微信 deep3321