Hive SQL执行流程
一条HQL进来,第一步,先通过SQL Parser,把SQL字符串转成抽象语法树,同时做语义分析,然后语义分析器把抽象语法树编译成查询块,
第二步,结合MeteStore里的元数据校验表存不存在,字段存不存在,
第三步,生成逻辑执行计划,然后优化器按规则做一轮重写,比如把过滤条件下推,、把用不到的列裁掉、把常量表达式先算出来,
第四步,转成物理执行计划,拆成一个个MapReduce任务,决定用几个Map几个Reduce,这时候基于代价的优化器会算一笔账,从几种Join方案里挑代价最小的。
第五步,执行,生成底层代码,提交到yarn上跑,算完把结果返回
Hive自定义函数流程,有什么作用
第一步,写Java类,继承GenericUDF,重写initialize、evaluate、getDisplayString 三个方法,
第二步,打成Jar包传到HDFS
第三步,注册 CREATE FUNCTION scott.HELLO_UDF AS ‘com.yjxxt.hive.udf.HelloUDF’ USING JAR ‘hdfs:///bd/hive-demo-1.0-SNAPSHOT.jar’,默认注册到default库
之后SQL里自定义函数就可以当系统函数直接用
Hive分区和分桶
分区是按字段值在表目录下再开一层子目录,目录名就是 分区键=键值,逻辑上分区表和普通表没区别,物理上数据按分区键值分目录存,查询带上where dt = ‘2026-09-02’就只扫这一个目录,分区裁剪,避免全表扫描,分区分静态和动态,还分单分区和多分区。
分桶是把数据按hash(字段)% 桶数拆成固定几个文件,好处有三个:数据分布更细更均匀,方便抽样,提升JOIN效率
Hive和传统数据库的区别
Hive 和传统数据库最本质的区别,是面向的场景不同。MySQL 是OLTP,联机事务处理,管的是前台业务一笔一笔的存取:要毫秒级响应、要事务、要能随时修改删除一行数据,数据量一般是 GB 级。Hive 是OLAP,联机分析处理,管的是海量历史数据的批量计算:一次查询扫 TB 级数据,跑几十秒到几分钟都很正常,不适合高并发,也没有行级更新。
具体差在几个地方。存储上,Hive 的数据在 HDFS 上,分散在整个集群各节点的磁盘里;传统数据库的数据文件就在本机块设备的本地文件系统上。数据更新上,Hive 早期根本不支持修改删除,数据是加载的时候就定好的,0.14 之后才支持事务,但前提很苛刻——ORC 格式加分桶再加 transactional=true。执行方式上,Hive 是把 SQL 翻译成 MapReduce 或 Spark 任务,丢到 YARN 上跑;数据库用自己的执行引擎,数据规模小的时候延迟很低。扩展性上,Hive 跟 Hadoop 一样是线性扩展,加机器就行;数据库受 ACID 限制,往上扩展收益有限。
Hive物化视图和视图的区别
普通视图是虚拟表,只存SQL定义,不存数据,每次查都重新把创建它的SELECT执行一遍,创建时结构会冻结,底层表删了改了视图就失效。
物化视图是把查询结果真正落一份数据的数据库对象,专门预计算表连接、聚合这类耗时操作,再查直接读结果。
Hive内部表和外部表
内部表 Hive既管元数据也管数据,外部表Hive只管元数据,表只是个指向外部目录的引用
Hive导出数据有几种方式
INSERT OVERWRITE DIRECTORY ‘/PATH’ SELECT … 导到HDFS目录
INSERT OVERWRITE TABLE 表名 SELECT …导到另一张Hive表
hdfs dfs -get / hdfs dfs -cp 把HDFS文件拉回本地或拷到别的目录
EXPORT TABLE t_person TO ‘/bd/export/person’ 元数据加数据一起导出
动态分区和静态分区
两者的区别,本质是分区值什么时候确定、由谁确定。
静态分区:分区值在 SQL 里写死,编译期就定了。比如 INSERT OVERWRITE TABLE t PARTITION(dt=‘2026-09-07’) SELECT …,所有数据都进这一个目录。
动态分区:分区值不写死,只写列名,比如 PARTITION(grade, clazz);Hive 执行时从 SELECT 结果集的最后几列取值,按值自动建目录——不同 grade、clazz 的数据自动落到各自目录。
一句话:静态是"手动指定",动态是"自动分流"。
sort by / order by / distribute by / cluster by
sort by 局部排序,每个Reducer内部有序,全局不一定有序,会视情况起多个Reducer,适合大数据集
order by 全局排序,最终结果整体有序,但底层所有数据进一个Reducer做排序,数据量大就成瓶颈,大数据集效率非常低。
distribute by 分区排序,只管分发不管排序,按指定字段做Hash散列控制某行去哪个Reducer,目的是让相同Key进同一个Reducer。
cluster by组合排序,当distribute by 和 sort by 字段相同时,组合排序等价于两者叠加,又分又排相当于一个语法糖,少写一点代码,但是他只能升序。
行式文件和列式文件
行式存储一行接一行存,一行数据在介质里连续存放,取一整行很快,新增和更新数据也方便,适合事务处理,但分析查询只查几列时,会把整行都读出来,不参与计算的列也照样IO,而且一行里各列类型不一,压缩比上不去。
列式存储按列存,同一列的值连续存放,特征是扫很多行,只查几列,因为只读参与计算的列,所以IO大幅降低,且cpu缓存命中率也高,并且同一个列类型也相同,压缩比高的多,存储和带宽都可以省,还能选择最合适的压缩算法。
常见SQL优化
按优先级过一遍:先用where把数据量压下去,能带分区条件就带,走分区裁剪。
不要select *,只取需要的列,配合列裁剪(投影下推)省IO
大表JOIN小表用Map Join,小表广播进内存建HashTable,避免大Shuffle,left join 左表必须为大表
别写出笛卡尔积,
减少Shuffle,聚合和join前先做局部聚合,开hive.map.aggr走Map端预聚合
count(distinct)改写成先groupby再count,因为它默认只用一个Reducer,容易数据倾斜
orderby改sortby + limit做top-N
数据倾斜原因
- 读了大文件,最常见为GZip压缩的大文件,因为它不可分割,只有一个Map读,换成BZip2/LZO支持切分的去解决
- 单表聚合时部分Key数据量特别大,且大Key分散在很多切片里,Shuffle后全压一个Reducer,
- 两表 JOIN 都含大量相同的倾斜 Key**,热点记录全落同一个 Reducer;
- 分组列有大量 NULL、空字符串**——空值在 Hash 分区里算同一个 Key,天然往一个 Reduce 挤;
- 聚合操作无法消减中间结果**(比如
COLLECT_LIST要收集全量数据),大量数据必须过 Shuffle。
追问|怎么解决?——空值问题成本最低:INNER JOIN Hive 会自动过滤 NULL(看 EXPLAIN 有predicate: deptno is not null),LEFT JOIN 不会,那就先WHERE key IS NOT NULL过滤再 JOIN;业务要保留 NULL 就给它拼随机前缀打散(NVL(deptno, CONCAT('NULL_', CAST(RAND()*100 AS INT)))),或者拆成两段 SQL 分别处理再 UNION ALL。聚合倾斜用两阶段加盐:先CONCAT(RAND()*10, '_', deptno)加盐做局部聚合,再去盐全局聚合。JOIN 倾斜用Skew Join(hive.optimize.skewjoin=true,超过hive.skewjoin.key默认 100000 的 Key 单独拆出来走 MapJoin,其余走 Common Join,最后合并),或者手动把倾斜 Key 的表拆成 Skew/Normal 两份分别处理再 UNION ALL。GROUP BY 倾斜可以开hive.groupby.skewindata拆两个 Job 做负载均衡。
Hive优化
Hive 优化分为四个维度:建表存储、SQL 逻辑、引擎参数、以及数据倾斜。万变不离其宗,核心心法就六个字:少读、少传、少算。
第一,建表与存储优化(少读)。
首选列式存储 ORC 或 Parquet,配合合适的压缩算法(如中间结果用 Snappy,极致存储用 Zstd)。同时结合业务做分区(按日期)和分桶,查询时利用分区裁剪和列裁剪,把不需要读的文件直接跳过。
第二,SQL 与执行计划优化(少算/少传)。
写 SQL 时,善用 RBO(谓词下推、常量折叠)和 CBO(基于代价优化 Join 顺序,小表先 Join)。能用 GROUP BY 代替 COUNT(DISTINCT);全局排序 ORDER BY 必须加 LIMIT(配合严格模式防呆)。另外,开启 Map 端预聚合(Combiner)减少 Shuffle 数据量。
第三,针对 Join 场景的专项优化。
优先级从低到高:
大表 Join 大表用 Common Join(走 Shuffle 最慢)。
小表 Join 大表用 Map Join(小表广播进内存,无 Shuffle)。
两表分桶且桶数成倍数,用 Bucket Map Join。
分桶且桶内排序,用 SMB Join(双游标归并,内存极小,速度极快)。
第四,数据倾斜与参数调优(兜底与防呆)。
如果出现大 Key 导致单节点 OOM,首选过滤空值或手动加盐打散;复杂场景直接开 Skew Join 自动拆解。参数层面,开启向量化执行(一次算1024行,榨干 CPU),开启 Shuffle 压缩(Snappy),合并小文件,小任务开启本地模式,最后用推测执行给慢节点兜底。
追问 1:Map Join 既然这么好,为什么不全用 Map Join?
答:Map Join 必须要求小表能放进每个 MapTask 的内存(默认 25MB,可调)。如果小表过大,广播会导致网络带宽打满,且每个节点内存 OOM。所以只适合“大表 Join 极小表”。
追问 2:开了向量化,为什么查询还是慢?
答:向量化依赖列式存储(ORC/Parquet)。如果底层是 TextFile 这种行式存储,或者查询里全是不支持向量化的复杂 UDF,开了也没用。另外还要看 CPU 核数和内存是否足够。
追问 3:SMB Join 为什么比 Map Join 还快?
答:Map Join 是把小表全量塞进内存。而 SMB Join 是两张表都分桶且排序,它的 MapTask 只要取出对应的“同一个桶”做双指针归并,内存占用极小,速度快,而且不怕两边数据量都很大。