尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Hive存储格式深度解析:从行存到列存,ORC与Parquet如何选型?

Hive存储格式深度解析:从行存到列存,ORC与Parquet如何选型? 1. 项目概述为什么Hive存储格式是数据湖的基石如果你用过Hive或者正在大数据领域摸爬滚打那你肯定对“建表”这个操作不陌生。建表语句里那个STORED AS子句后面跟着的TEXTFILE、ORC、PARQUET就是今天要聊的核心——Hive数据存储格式。这玩意儿远不止是一个简单的文件后缀名它直接决定了你的数据在HDFS上怎么“躺”进而深刻影响查询速度、存储成本和运维复杂度。我见过太多项目初期为了图省事所有表都用默认的TextFile结果数据量一上来查询慢得像蜗牛存储成本也蹭蹭往上涨后期再想转格式那真是伤筋动骨。简单来说选对存储格式就像给数据仓库打地基。地基打得好上层建筑即SQL查询、数据分析才稳当、高效。这次我就结合自己踩过的坑和实战经验把Hive里主流的几种存储格式掰开揉碎了讲清楚从最原始的TextFile到高效的列式存储ORC/Parquet再到一些你可能没怎么用过的“老古董”如SequenceFile、RCFile。我会重点讲清楚它们各自的原理、适用场景以及最关键的选择依据和实操中的那些“坑”。2. 存储格式核心原理与演进脉络要理解不同格式的优劣得先明白两个核心概念行式存储和列式存储。这是所有存储格式设计的根本出发点。2.1 行式存储 vs 列式存储两种根本性的数据组织哲学你可以把一张表想象成一个Excel表格。行式存储就像我们平时读写表格的习惯从左到右处理完一行的所有列再跳到下一行。在物理存储上同一行的数据是紧挨在一起的。比如(1, ‘张三’, 28, ‘销售’)这条记录在磁盘上可能就是连续存储的。行存的优势非常明显写入友好插入一条新记录只需要在文件末尾追加一串连续的数据即可效率很高。整行读取快如果你需要频繁进行SELECT *操作或者根据主键查询整行数据OLTP场景如订单查询行存可以一次性读出整行速度很快。但是在大数据分析OLAP场景下行存的劣势就暴露了。假设你的表有100列而你的查询只关心其中的3列例如SELECT department, AVG(salary) FROM employee GROUP BY department。行存引擎不得不从磁盘上读取每一行即100列的数据然后再从中过滤出需要的department和salary列。这产生了大量的I/O浪费因为磁盘读取了太多不必要的数据。列式存储则反其道而行之。它把同一列的数据存储在一起。还是上面那张表它会把所有行的id值存成一个数据块所有行的name值存成另一个数据块以此类推。列存的优势正是为分析查询量身定做的极高的压缩比同一列的数据类型相同数据模式相似比如都是年龄数字或者都是状态枚举值使用专门的压缩算法如字典编码、行程编码效率极高能大幅节省存储空间。极少的I/O对于只涉及少数列的查询存储引擎只需要读取相关的列数据块跳过了其他不相关的列I/O效率成倍提升。更好的向量化计算现代CPU支持SIMD指令可以一次性处理一批数据。列存数据排列整齐非常适合这种批处理模式进一步提升计算性能。当然列存也有缺点写入时需要将一行数据拆散到不同的列块中开销较大如果要重组整行记录也需要从不同位置收集数据有一定代价。但对于以读为主、批量写入的数据仓库场景列存的优势是压倒性的。2.2 Hive存储格式演进简史理解了行列存储再看Hive格式的演进就清晰了TextFile最初的支持纯文本行存。简单通用但无压缩、无优化。SequenceFileHadoop生态早期的二进制行存格式支持块压缩比TextFile好但仍是行存。RCFileHive提出的行列混合存储的过渡方案。它先将数据水平切分成多个行组在每个行组内部数据按列存储。这试图在行存和列存之间取得平衡但设计较为复杂性能优化有限。ORC Parquet现代大数据分析的主流列式存储格式。它们吸收了RCFile的思想并大幅优化提供了更高效的压缩、编码、索引和谓词下推等高级特性是目前事实上的标准。注意RCFile现在已基本被ORC/Parquet取代除非维护非常古老的历史系统否则不建议在新项目中使用。但了解它有助于理解列式存储的演进思路。3. 五大存储格式深度解析与实战对比接下来我们进入实战环节逐一拆解每种格式。3.1 TextFile最原始的双刃剑TextFile是Hive默认的存储格式如果不指定STORED AS。数据以纯文本形式存储每行就是一条记录字段间通常用分隔符如\001,,,\t隔开。创建表示例CREATE TABLE log_data ( ip STRING, access_time STRING, url STRING, status INT ) ROW FORMAT DELIMITED FIELDS TERMINATED BY \t -- 指定制表符分隔 STORED AS TEXTFILE; -- 可省略因为这是默认值优点人类可读直接用cat、head命令就能查看文件内容调试非常方便。通用性强几乎所有工具都支持读写文本文件数据交换无障碍。简单直接没有复杂的编码解码过程。缺点无压缩存储空间占用最大。当然你可以使用Gzip、Bzip2等压缩算法压缩整个TextFile通过SET hive.exec.compress.outputtrue;等配置但压缩后的文件不可切分可能影响MapReduce并行度。无类型信息所有数据都以字符串形式存储查询时需要进行类型转换有额外开销。解析开销大每次读取都需要按分隔符解析字段CPU消耗高。适用场景原始数据导入/导出如日志原始文件。数据量极小或临时中间表。需要频繁用非Hive工具查看内容的场景。实操心得对于ETL过程中的初始落地表用TextFile加Gzip压缩是一个不错的起点因为它通用且节省空间。但切记如果这个表后续要被频繁查询一定要将其转换为ORC或Parquet格式。一个常见的做法是原始日志TextFile - 清洗转换 - 写入ODS层ORC/Parquet。3.2 SequenceFileHadoop生态的“老将”SequenceFile是Hadoop设计的一种二进制键值对文件格式。在Hive中key一般为行号可忽略value就是行内容。创建表示例CREATE TABLE seq_table ( id INT, data STRING ) STORED AS SEQUENCEFILE;优点二进制存储比TextFile更紧凑。支持块压缩压缩效率高且文件是可切分的不影响并行处理。这是相比压缩TextFile的一大优势。支持分割适合作为MapReduce任务的输入输出格式。缺点仍是行存不具备列存的查询优势。非人类可读必须用专门的工具如hadoop fs -text查看。生态支持度下降在ORC/Parquet成为主流后SequenceFile的使用场景在减少。适用场景小文件合并。可以将大量小的TextFile合并成少量大的SequenceFile解决HDFS小文件问题。作为MapReduce复杂作业的中间存储格式。一些遗留系统或特定工具要求使用此格式。3.3 RCFile行列混合存储的探索RCFile的全称是Record Columnar File。它的设计很巧妙数据首先被水平划分为多个行组Row Group默认是4MB。在每个行组内部数据先按行存储再按列存储。可以理解为“先按行分块块内再按列存”。优点兼顾行列优点在行组级别它能快速定位到一组记录在行组内部因为列存它具备一定的压缩和查询优化能力。谓词下推支持基本的谓词下推可以跳过一些不满足条件的行组。缺点设计复杂性能折中它的优化效果不如纯粹的、设计更现代的列式格式ORC/Parquet。社区支持弱目前Hive社区的主要精力都在ORC上RCFile的优化和新特性很少。实操心得除非你在维护一个多年前就使用RCFile的历史集群并且迁移成本极高否则在任何新项目中都没有理由再选择RCFile。直接使用ORC或Parquet是更优解。3.4 ORCHive亲生的高性能列式格式ORC是Hive社区自研的、为Hive量身定做的列式存储格式可以说是“亲儿子”。它的全称是Optimized Row Columnar。核心特性索引文件级、条带Stripe级、行组级的三级索引。包含每列的最小值、最大值、行计数等可以快速跳过不相关的数据块。轻量级索引在ORC文件中你可以指定建立布隆过滤器Bloom Filter对于等值查询如WHERE user_id 123过滤效果极佳。ACID支持ORC是Hive实现事务性ACID表的基础存储格式支持INSERT、UPDATE、DELETE。复杂类型支持对Hive的STRUCT、MAP、ARRAY、UNION等复杂数据类型支持非常好。创建表与性能调优示例CREATE TABLE orc_table ( user_id BIGINT, username STRING, actions ARRAYSTRUCTaction_time:STRING, page:STRING ) STORED AS ORC TBLPROPERTIES ( ‘orc.compress’‘SNAPPY’, -- 压缩算法可选NONE, ZLIB, SNAPPY ‘orc.stripe.size’‘67108864’, -- 条带大小默认64MB ‘orc.row.index.stride’‘10000’, -- 行索引跨度每隔多少行建一个索引项 ‘orc.create.index’‘true’, -- 创建索引 ‘orc.bloom.filter.columns’‘user_id’ -- 为user_id列创建布隆过滤器 );优点查询性能极佳凭借索引和谓词下推对Hive SQL优化器非常友好。压缩率高通常能达到TextFile的1/10甚至更高。与Hive集成度最高支持所有Hive特性包括事务、更新删除、动态分区等。缺点生态相对封闭虽然Spark、Presto等主流引擎都已支持读写ORC但其在Hadoop生态外的普及度略低于Parquet。Schema演化支持虽然支持但不如Parquet那样被广泛认为是标准。适用场景Hive作为绝对核心的数据仓库。如果你的技术栈以Hive为中心ORC是最自然、性能最好的选择。需要用到Hive ACID事务功能的场景。查询模式复杂需要依赖Hive强大优化器的场景。3.5 Parquet跨平台生态的列式标准Parquet是由Twitter和Cloudera联合发起灵感来自Google的Dremel论文。它设计之初就强调跨语言、跨平台的通用性。核心特性列式存储纯粹的列式存储支持嵌套数据结构。Schema演化对Schema变更增加列、删除列、修改列类型的支持非常好是数据湖架构中常用的特性。广泛的生态支持几乎是所有大数据处理引擎的“一等公民”包括Spark、Flink、Impala、Presto、Hive等支持度无与伦比。高效的编码针对不同的数据类型使用了多种编码方案如字典编码、位打包、行程编码等。创建表示例CREATE TABLE parquet_table ( event_date DATE, user_id INT, event_name STRING, properties MAPSTRING, STRING ) STORED AS PARQUET TBLPROPERTIES ( ‘parquet.compression’‘SNAPPY’, -- 压缩算法 ‘parquet.block.size’‘134217728’ -- 块大小默认128MB );优点无与伦比的生态兼容性如果你使用多种引擎如Spark做ETLHive做即席查询Presto做交互分析Parquet是保证数据互通性的最佳选择。出色的压缩和查询性能与ORC在伯仲之间在不同场景下互有胜负。Schema演化非常适合数仓中缓慢变化的维度表或者数据湖中Schema不确定的场景。缺点对Hive特有功能支持稍弱早期对Hive复杂数据类型支持不如ORC但现在差距已很小。不支持ORC那样的轻量级索引如Bloom Filter。在纯Hive环境下某些极端优化可能不如ORC。适用场景多引擎混合技术栈。例如Spark Flink Hive Presto。数据湖如Delta Lake、Apache Iceberg的底层存储格式。这些项目通常首选Parquet。需要频繁进行Schema变更的场景。4. 格式选型决策指南与性能实测了解了原理和特性到底该怎么选我总结了一个决策矩阵。特性/需求TextFileSequenceFileRCFileORCParquet人类可读优差差差差通用性优中Hadoop生态内差良Hive生态优优全生态压缩率差或依赖通用压缩良良优优查询性能差中中优Hive下优写入速度优良中中中Schema演化无无无支持优ACID事务不支持不支持不支持支持不支持依赖上层主要场景原始数据、交换小文件合并、中间格式不推荐Hive数仓核心表跨引擎、数据湖选型建议单一Hive仓库优先选择ORC。它能最大化利用Hive的优化能力压缩和查询性能最好且支持ACID。混合引擎/数据湖优先选择Parquet。它是连接Spark、Flink、Presto、Impala等组件的“最大公约数”能避免未来数据孤岛。ETL起点或数据交换使用TextFile可压缩或SequenceFile。它们简单通用作为数据管道的第一站很合适。历史遗留系统如果已经是RCFile评估迁移成本。成本高则维持否则逐步迁移至ORC/Parquet。绝对禁止不要在生产系统的核心分析表上使用TextFile。性能实测小技巧光说不练假把式。当你犹豫时最好的方法是用自己的数据和查询做一次实测。准备一份有代表性的数据集比如几亿条记录。分别用TextFile、ORC、Parquet创建结构相同的表并导入数据。记录存储空间占用。准备3-5个典型的分析查询如带过滤的聚合、多表关联。在每个表上依次运行查询记录查询耗时。记得每次运行前清空缓存hive命令行里可以reset或者重启会话。对比数据结果会非常直观。我自己的测试中ORC/Parquet的查询速度通常是TextFile的5-10倍以上存储空间只有1/5到1/10。5. 高级技巧、常见问题与避坑指南掌握了基本选型再来看看实战中的高级玩法和那些容易踩的坑。5.1 高级配置调优存储格式的性能很大程度上取决于配置参数。ORC调优关键参数orc.stripe.size条带大小。增大如256MB可以提高顺序I/O效率适合大扫描查询减小可以提高随机读的粒度。默认64MB是个不错的平衡点。orc.row.index.stride行索引跨度。默认10000。减小此值可以生成更精细的索引加快点查但会增加索引文件大小。如果很少有WHERE id xxx的查询可以适当调大。orc.compress压缩算法。SNAPPY在压缩速度和比率间取得平衡最常用。ZLIB压缩率更高但更耗CPU。NONE用于调试。orc.bloom.filter.columns为高基数列如user_id创建布隆过滤器对等值过滤有奇效。Parquet调优关键参数parquet.block.sizeHDFS块大小也即Parquet的读写单元。通常与HDFS块大小对齐如128MB, 256MB。parquet.page.size页是编码和压缩的最小单元。默认1MB。对于有很多行的表保持默认即可对于宽表列很多可能需要减小以避免单个页过大。parquet.compression同ORCSNAPPY是通用选择。5.2 常见问题与解决方案实录问题1从TextFile向ORC/Parquet迁移时任务失败或极慢。现象执行INSERT OVERWRITE TABLE orc_table SELECT * FROM text_table;时卡住或报错。排查检查源TextFile是否被压缩如.gz。压缩文件不可切分会导致只有一个Map任务处理速度极慢。解决方案先将压缩文件解压或者使用LZO等支持切分的压缩格式。检查数据量。如果一次性迁移TB级数据可能导致单个任务过载。解决方案启用动态分区或者按分区分批迁移。检查资源。转换格式是计算密集型任务确保Yarn有足够的内存和CPU资源。可以调大Map/Reduce任务的内存set mapreduce.map.memory.mb4096;等。我的经验大规模迁移一定要分批分区进行。先迁移最近的热分区再迁移历史冷数据。迁移过程中监控Yarn资源队列避免打满集群影响线上任务。问题2查询ORC/Parquet表时SELECT *依然很慢。现象以为用了列存SELECT *就会快但实际不然。原因SELECT *需要读取所有列列存的I/O优势无法发挥。如果表很宽几百列即使列存读取全部数据量也很大。解决方案根本解决审视业务真的需要所有列吗尽量指定需要的列。技术优化对于ORC可以检查是否启用了predicate pushdown默认开启。确保WHERE条件中的字段是建了索引的。对于Parquet可以检查parquet.enable.dictionary是否开启默认开字典编码对文本列过滤有帮助。问题3小文件问题。现象使用STORED AS PARQUET或ORC后发现HDFS上生成了大量小文件比如几MB一个严重影响NameNode性能和查询效率。原因通常是因为写入作业的Reduce任务数过多或者使用了动态分区且每个分区数据量很小每个任务/分区都写了自己的文件。解决方案合并小文件使用Hive的CONCATENATE命令仅适用于ORC/RCFile或ALTER TABLE ... [PARTITION] CONCATENATE;。或者写一个INSERT OVERWRITE语句重新写入数据通过控制Reduce数量set hive.exec.reducers.bytes.per.reducer256000000;来产生合适大小的文件。写入时优化在ETL任务的最后一步通过DISTRIBUTE BY某个字段将数据重分布减少输出文件数。例如INSERT ... SELECT ... DISTRIBUTE BY floor(rand()*10)将数据随机散列到10个Reducer上。对于Spark可以使用coalesce或repartition控制输出分区数。问题4Schema变更增加列后新查询读不到老数据的新字段。现象给Parquet表增加了一个新列new_col但查询老分区时new_col的值全是NULL。原因Parquet文件在写入时就将Schema列名和类型固化在文件元数据中。老数据文件里根本没有这个列。解决方案重写历史数据成本高但一劳永逸。INSERT OVERWRITE TABLE my_table PARTITION(dt) SELECT ..., NULL AS new_col, ... FROM my_table;使用兼容性查询这是更常见的做法。利用Parquet的Schema合并特性。确保新老Schema兼容只增不减然后在查询时设置set parquet.merge.schematrue;。这样查询老数据时新列会以NULL值或默认值出现。注意ORC也有类似特性但具体配置参数不同hive.orc.schema.evolution。5.3 格式转换实战脚本示例这里分享一个我常用的、将历史TextFile分区表安全转换为ORC格式的脚本模板。它采用逐分区转换、验证数据一致性、最后切换视图的方式最大限度保证业务无感知。-- 1. 创建目标ORC表结构与原表一致 CREATE TABLE my_table_orc ( ... -- 列定义与原表完全一致 ) PARTITIONED BY (dt STRING) STORED AS ORC TBLPROPERTIES (‘orc.compress’‘SNAPPY’); -- 2. 动态插入转换数据示例转换最近7天的分区 SET hive.exec.dynamic.partitiontrue; SET hive.exec.dynamic.partition.modenonstrict; INSERT OVERWRITE TABLE my_table_orc PARTITION(dt) SELECT ... -- 所有字段 , dt FROM my_table_text -- 原TextFile表 WHERE dt date_sub(current_date, 7); -- 限制转换范围 -- 3. 数据一致性验证记录数对比 SELECT dt, count(*) as cnt FROM my_table_text WHERE dt date_sub(current_date, 7) GROUP BY dt; SELECT dt, count(*) as cnt FROM my_table_orc GROUP BY dt; -- 手动对比两个查询结果确保一致 -- 4. 创建统一视图指向新表 CREATE VIEW my_table AS SELECT * FROM my_table_orc UNION ALL SELECT * FROM my_table_text WHERE dt date_sub(current_date, 7); -- 未转换的历史分区仍读原表 -- 5. 通知业务方将查询从 my_table_text 改为 my_table (视图)。 -- 6. 后续可逐步转换更早的历史分区并更新视图定义最终所有分区转换完毕后视图可直接指向 my_table_orc原表可归档删除。这个流程的关键在于平滑过渡通过视图屏蔽底层存储变化业务查询无需修改。转换完成后存储和查询性能的提升是立竿见影的。最后关于存储格式的选择没有银弹。但一个核心原则是面向你的查询模式和数据生命周期做设计。对于频繁扫描分析的热数据列式存储ORC/Parquet是不二之选对于数据交换或初始落地的冷数据文本或序列文件也未尝不可。理解其原理结合业务实测你就能为你的数据找到最合适的“家”。
返回列表