5.1 数据仓库工具 Hive 本节摘要:Hive 把 HDFS 上的文件映射成表结构,把 SQL 翻译成 MapReduce 或 Tez 作业,让不写代码的分析师也能查 PB 级数据。本节覆盖元数据映射原理、分区与分桶设计、SQL 到物理计划的翻译链路,以及用执行计划定位慢查询的方法。 表是目录,行是文件 Hive 最核心的洞察只有一句:结构是视角,不是拷贝。一份躺在 HDFS 的 TSV 文件,Hive 并不把它搬进自己的存储,只在 Metastore(一个关系库,通常 MySQL)里记一张"映射单":表名到 HDFS 路径、列名到字段位置、分隔符是什么。查表时,Hive 去对应目录读文件、按分隔符切列、按你的 SQL 生成分布式作业。 这张映射单带来两个直接红利。
本节摘要:Hive 把 HDFS 上的文件映射成表结构,把 SQL 翻译成 MapReduce 或 Tez 作业,让不写代码的分析师也能查 PB 级数据。本节覆盖元数据映射原理、分区与分桶设计、SQL 到物理计划的翻译链路,以及用执行计划定位慢查询的方法。
Hive 最核心的洞察只有一句:结构是视角,不是拷贝。一份躺在 HDFS 的 TSV 文件,Hive 并不把它搬进自己的存储,只在 Metastore(一个关系库,通常 MySQL)里记一张"映射单":表名到 HDFS 路径、列名到字段位置、分隔符是什么。查表时,Hive 去对应目录读文件、按分隔符切列、按你的 SQL 生成分布式作业。
hdfs://warehouse/dt=2026-08-18/part-00000.tsv ↓ Metastore映射单 表 access_log (dt STRING, user STRING, url STRING) PARTITIONED BY (dt) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'
这张映射单带来两个直接红利。其一,一份底层数据可以挂多个视角(外部表),采集层与数仓层解耦——删表定义不删数据(EXTERNAL 表),避免误删事故。其二,Beeline/HiveServer2 提供标准 JDBC 接入,BI 报表工具即插即用。
CREATE EXTERNAL TABLE access_log ( ts STRING, user_id STRING, url STRING, bytes INT ) PARTITIONED BY (dt STRING) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t' LOCATION '/data/access'; -- 关联分区目录(分区是目录名约定) ALTER TABLE access_log ADD PARTITION (dt='2026-08-18');

分区与分桶都是"把第 3 章学过的全量扫描,提前裁剪掉"的手段,但作用层次不同。
分区(PARTITIONED BY)是目录级裁剪。dt=2026-08-18 是一个子目录,查询带上 WHERE dt='2026-08-18' 时 Hive 直接只扫该目录——分区裁剪把 IO 从全量表降到单日量。设计原则按查询模式选分区键:日报表场景按天分区是标配;常按小时查就再切一层。警惕过度分区:每个分区至少一个文件,按"天×城市×渠道"三层切分区,三年下来可能几十万个空文件——2.4 节的小文件账本立刻爆掉。经验:分区数控制在万级以内,粒度到天为宜。
分桶(CLUSTERED BY ... INTO N BUCKETS)是文件内组织。按某列 hash 成 N 个文件,价值有三:join 前若两表都按 join 键分桶且桶数成倍数,可走排序合并 join,跳过 Shuffle 全量重分布(第 3 章最贵的一段直接省掉);抽样查询 TABLESAMPLE(BUCKET 1 OUT OF 64) 只读一个文件;数据分布更均匀,倾斜概率下降。
CREATE TABLE user_agg ( user_id STRING, pv BIGINT, bytes BIGINT ) CLUSTERED BY (user_id) SORTED BY (user_id) INTO 64 BUCKETS;
SELECT user_id, sum(bytes) FROM access_log WHERE dt='2026-08-18' GROUP BY user_id 在 Hive 内部走六步:
看懂这条链,慢查询优化就有了坐标系。EXPLAIN 是显微镜:
EXPLAIN SELECT user_id, sum(bytes) FROM access_log WHERE dt='2026-08-18' AND bytes > 1000 GROUP BY user_id; -- 计划要点(节选): -- Stage-1 Map: filterExpr: ((dt = '2026-08-18') and (bytes > 1000)) -- expressions: user_id, bytes -- → 分区裁剪已生效(只扫1个目录) -- → bytes>1000 谓词下推到Map端 -- → Group By Operator: aggregations: sum(bytes) keys: user_id -- mode: mergepartial ← Map端已做部分聚合(Combiner逻辑) -- Stage-0 Fetch: 输出
读计划三个要点:确认分区裁剪出现在 filterExpr(没出现就是全表扫,检查分区列上有没有函数包裹——WHERE substr(dt,1,7)='2026-08' 会让裁剪失效);看聚合 mode 是否 partial(Map 端预聚合开了没);看有没有意外的 reduce 边数暴涨(join 键倾斜时 Stage 数异常多)。
Map 端聚合:hive.map.aggr=true 默认开,等价于第 3 章 Combiner——hash 分组在 Map 内存先聚合,group by 键基数(不同值个数)极大时(如按 URL group by)反而内存吃紧,需关闭或加大 hive.groupby.mapaggr.checkinterval。
join 选择:小表广播 join(mapjoin)自动开启阈值 hive.auto.convert.join.noconditionaltask.size(默认 10MB 级),对应第 3 章 DistributedCache 广播连接;两表都按 join 键分桶时走 SMB join,免 Shuffle。
倾斜兜底:hive.optimize.skewjoin=true 让超出阈值的热点 key 单独走一轮作业——正是 3.4 节加盐策略的自动化版。
文件格式:TEXTFILE 人读友好但读时解析贵;ORC/Parquet 列存格式只读查询涉及的列(列裁剪真正落到 IO 层面),自带轻量索引与高压缩比。生产数仓的分层表(明细层起)一律 ORC 是行业共识,CREATE TABLE ... STORED AS ORC 一行搞定。列存对第 5 章所有分析型负载是数量级收益:40 列的宽表只查 3 列,IO 直接省一个数量级。
下一节看一个更接近手工作坊的工具:Pig 的数据流脚本。