0. 思维导图

1. 简述Hive♥♥

  我理解的,hive就是一款构建数据仓库的工具,它可以就结构化的数据映射为一张表,并且可以通过SQL语句进行查询分析。本质上是将SQL转换为MapReduce或者spark来进行计算,数据是存储在hdfs上,简单理解来说hive就是MapReduce的一个客户端工具。

  • 补充1:你可以说一下HQL转换为MR的任务流程吗?♥♥♥
    • 首先客户端提交HQL以后,hive通过解析器将SQL转换成抽象语法树,然后通过编译器生成逻辑执行计划,再通过优化器进行优化,最后通过执行器转换为可以运行的物理计划,比如MapReduce/spark,然后提交到yarn上执行
    • 详细来说:
      • 首先客户端提交SQL以后,hive利用Antlr框架对HQL完成词法语法解析,将HQL转换成抽象语法树
      • 然后遍历AST,将其转换成queryblock查询块,可以理解为最小的查询执行单元,比如where
      • 然后遍历查询块,将其转换为操作树,也就是逻辑执行计划。
      • 然后遍历优化器对操作树进行逻辑优化,源码中会遍历所有的优化方式,比如mapjoin,谓词下推等,来达到减少MapReduce Job,减少shuffle数据量的目的。
      • 最后通过执行器将逻辑执行计划转换为物理执行计划(MR到这就结束了)(spark还需要使用物理优化器对任务树进行物理优化),提交到hadoop集群运行。
  • 补充2:你可以说一下hive的元数据保存再哪里吗?
    • 默认是保存java自带的derby数据库,但是这有一个缺点:derby数据库不支持并发,也就是说不能同时两个客户端去操作derby数据库,因此通常情况下,都会配置一个mysql去存放元数据。

2. 简述Hive读写文件机制

  • 读取文件:

    • 首先调用InputFormat(默认TextInputFormat)对文件进行逻辑切片,返回一条一条的kv键值对,然后调用SerDe(LazySimpleSerDe)的反序列化方法,将一条记录中的value根据分隔符切分为各个对应的字段。
  • 写文件:

    • 首先调用SerDe(默认LazySimpleSerDe)的序列化方法将对象序列化为字节序列,然后调用OutputFormat将数据写入HDFS文件中。

3. Hive和传统数据库之间的区别♥♥♥

  • 我认为主要有散点的区别:

    • 数据量,hive支持大规模的数据计算,mysql支持的小一些
    • 数据更新快不快,hive官方是不建议对数据进行修改的,因为非常的慢,这一点我也测试过,而mysql经常会进行数据修改,速度也挺快的。
    • 查询快不快,hive大多数延迟都比较高,mysql会低一些,当然这也与数据规模有关,数据规模很大的时候,hive不一定比mysql慢。
  • 为什么处理小表延迟比较高:

    • 因为hive计算是通过MapReduce,而MapReduce是批处理,高延迟的。hive的优势在于处理大数据,对于处理小数据是没有优势的。

4. Hive的内部表和外部表的区别♥♥♥

从建表语句来看,加上了external关键字修饰的就是就是外部表,没加的就是内部表。

  • 我认为主要有两点的区别:

    1. 内部表的数据由hive自身管理,外部表的数据由hdfs管理。
    2. 删除内部表的时候,元数据和原始数据都会被删除,而删除外部表的时候仅仅会删除元数据,原始数据不会被删除。
  • 使用场景:通常都会建外部表,因为一个表通常要多个人使用,以免删除了,还可以找到数据,保证了数据安全。

5. Hive静态分区和动态分区的区别

分区表,也叫分区裁剪,就是分目录,作用就是减少全表扫描。
建表的时候:partitioned by (day string) 加载数据的时候:partition(day=“20210823”)【静态分区】或者partition(day)【动态分区】。
分区字段不能是表中已经存在的字段。

  • 静态分区:

    • 分区字段的值是在导入数据的时候手动指定的
    • 导入数据的方式可以是load data方式,也可以是insert into + select 方法
  • 动态分区:

    • 分区字段的值是基于查询结果自动推断出来的,也就是最后查询结果的最后一个字段值就对应分区字段的值。
    • 导入数据的方式必须是insert into + select 方式
    • 想使用动态分区表的时候必须要对hive进行两个配置
      1. 开启动态分区功能 hive.exec.dynamic.partition=true
      2. 设置动态分区的模式为非严格模式,也就是说允许所有分区字段都可以使用动态分区hive.exec.dynamic.partition.mode=nonstrict
  • 补充题:你知道分桶表吗,谈谈这两个的区别?

    • 分桶表和分区表的作用都是用来减少全表扫描的,那么有了分区表,为啥还要有分桶表呢?

    • 因为并非所有的数据都可以进行合理的分区,所以有了新的技术分桶表

      • 分桶表的分桶规则是,根据分桶字段的hash值,对桶的个数进行取余运算,然后得到该数据应该放到哪个桶里面去
    • 说了这么多,他们有什么区别呢?

      1. 创建语句不同,分区表是partitioned by,分桶表是clustered by
      2. 分区或分桶字段要求不同,分区字段不能是表中存在的字段,分桶字段一定是表中存在的字段。
      3. 表现形式不同,分区表其实就是分了目录存放数据,分桶表是将一个文件拆分为很多文件存放。

6. 内连接、左外连接、右外连接的区别

  • 内连接:返回的是两个表的交集。
  • 左外连接:返回左表的所有行,如果左表的某行在右表没有匹配行,则将右表返回空值。
  • 右外连接:返回右表的所有行,如果右表的某行在左表没有匹配行,则将左表返回空值。

7. Hive的join底层实现♥♥♥

  • 首先hive的join分为common join 和map join,common join 就是join发生在reduce端,map join就是join发生在map端

  • common join:

    • 分为三个阶段:map阶段、shuffle阶段、reduce阶段
      • map阶段:对来自不同表的数据打标签,然后用连接字段作为key,其余部分和标签作为value,最后进行输出
      • shuffle阶段:根据key的值进行hash,这样就可以将key相同的送入一个reduce中
      • reduce阶段:对来自不同表的数据进行join操作就可以了
  • map join:

    • 首先它是有一个适用前提的,适用于小表和大表的join操作
    • 小表多小为小呢?所有就有了一个参数进行配置:hive.mapjoin.smalltable.filesize=25M
    • 它的原理是将小表复制多份,让每个map task内存存在一份,比如我们可以存放到HashMap中,然后join的时候,扫描大表,对于大表中的每一条记录key/value,在HashMap中查找是否有相同的key的记录,如果有,则join连接后输出即可,因为这里不涉及reduce操作。
    • 0.7版本之后,都会自动转换为map join,如果之前的版本,我们配置一个参数就可以了:hive.auto.convert.join=true。

8. Order By 和 Sort By的区别♥♥

distribute by: 将数据根据by的字段散列到不同的reduce中
cluster by:当distribute by 和sort by 字段相同的时候,等价于cluster by ,但是排序只能是升序。

  • order by:全局排序,只有一个reducer,缺点:当数据规模大的时候,就会需要很长的计算时间。
  • sort by:分区排序,保证每个reducer内有序,一般结合distribute by来使用
  • 使用场景:在生产环境中,order by 用的比较少,容易导致OOM;一般使用distribute by + sort by

9. 行转列和列转行函数♥

JSON解析函数:

  1. get_json_object:每次只能返回json对象中的一列值 select get_json_object(data,‘$.movie’) as movie from json;
  2. json_tuple:每次可以返回多列的值 select b.b_movie, b.b_rate,b.b_timeStamp,b.b_uid from json lateral view json_tuple(json.data,‘movie’,‘rate’,‘timeStamp’,‘uid’) b as b_movie,b_rate, b_timeStamp,b_uid;
    如果是json数组的话,那么就不能直接使用上述的操作,我们可以先使用regexp_replace方法进行字符串的替换,将它处理成多个json,然后再使用上述的方法就可以了。
    URL解析函数:HOST QUERY
  3. parse_url:一对一
  4. parse_url_tuple:一对多
  • 常见的行转列包括:常见的行转列包括:一般的聚合函数,比如max,min,sum:还有汇总函数,比如collect_list,collect_set

  • 常见的列转行就是:explode函数(json_tuple函数),只能传入array或者map的数据,将它拆分成多行,一般会和lateral view一起使用。

    • select movie, category_name from movie_info lateral view explode(split(category, “,”)) movie_info_tmp as category_name
  • 窗口函数:

    • Rand:

      • rank(): 排序相同的时候,排名会重复,总数不变
      • dense_rank():排序相同的时候,排名会重复,总数减少
      • row_number():排序相同的时候,排名不会重复,总数不变
    • lag(col, n, default):返回往上移n行的数据,不存在则返回default

    • lead(col,n, default):返回往下移n行的数据,不存在则返回default

    • first_value(col):取分组内排序后,第一个值

    • last_value(col):取分组内排序后,最后一个值

  • over用法:首先通过over来指定窗口的特性,比如可以传入partition by(分组),order by(排序),rows between … and … 指定窗口的范围

    • CURRENT ROW:当前行
    • n PRECEDING/FOLLOWING: 往前/后n行数据
    • UNBODUNDED PRECEDING/FOLLOWING:表示从前面的起点/到后面的终点
    • 默认是rows between UNBOUNDED PRECEDING and current row

10. grouping_sets、cube和rollup

他们都是用于group by后面的一个函数,作用是将不同维度的group by进行简化。

  • grouping_sets(字段1, 字段2)会对字段1和字段2分别分组聚合,然后UNION ALL
  • cube(字段1,字段2)会对字段1和字段2的所有组合2的n次方种分别分组聚合,然后UNION ALL with cube
  • rolllup 是cube的一个子集,rollup会以最左侧的维度为主with rollup

11. 自定义过UDF、UDTF函数吗♥♥♥

  1. 自定义函数
    (1)自定义UDF:

    1. 继承UDF
    2. 重写evaluate方法

    (2)自定义UDTF:

    1. 继承GernericUDTF
    2. 重写3个方法:initialize,process,close
  2. 打成jar包,上传到服务器中

  3. 执行命令:add jar “路径”,目的是将jar添加到hive中

  4. 注册临时函数:create temporary function 函数名 as “自定义函数全类名”

12. Hive3的新特性有了解吗

  • 物化视图:
    • 简述:和普通视图的不同点在于普通视图不保存数据,仅仅保存查询语句,而物化视图是把查询的结果存入到了磁盘中,它的作用是通过预计算保存好一些复杂的计算结果,提高查询效率
    • 语法: create materialized view … as select …
  • 在向动态分区表中导入数据的时候,也可以使用load文件的方式,因为底层会自动转换为inser + select 语句

13. Hive小文件过多怎么办♥

  • 首先我说一下为什么会产生小文件呢

    • hive中产生小文件就是在向表中导入数据的时候,通常来说,我们在生产环境下,一般会使用insert+select的方式导入数据,这样会启动MR任务,那么reduce有多少个就会输出多少个文件,也就是说insert每执行一次啊,就至少生产一个文件,有些场景下,数据同步可能每10分钟就会执行一次,这样就会产生大量的小文件。
  • 然后我再说一下问什么要解决小文件呢?不解决不行吗?

    • 首先对于hdfs来说,不适合存储大量的小文件,文件多了,namenode需要记录元数据就非常大,就会占用大量的内存,影响hdfs存储性能
    • 对于hive来说,每个文件会启动一个maptask来处理,这样也会浪费计算资源。
  • 最后说一下怎么解决:

    • 使用hive自带的concatenate命令合并小文件,但是它只支持recfile和orc存储格式
    • MR过程中合并小文件
    • map前:
      • 设置inputformat为combinehiveinputformat:在map的时候会把多个文件作为一个切片输入
    • map后,reduce前:
      • map输出的时候合并小文件 hive.merge.mapfiles
    • reduce 后
      • reduce输出的时候合并小文件hive.merge.mapredfiles
    • 直接设置少一点的reduce数量mapreduce.job.reduces
    • 使用hadoop的archive归档方式

14. Hive优化♥♥♥

  • 建表优化:

    • 分区表:减少全表扫描,通常查询的时候先基于分区过滤,再查询

    • 分桶表:按照join字段进行分桶,join的时候就不会全局join,而是桶与桶之间进行join

    • 合适的文件格式:公司中默认采用的是ORC的存储格式,这样可以降低存储空间,内部有两个索引(行组索引和布隆过滤器索引)的东西,可以加快查询速度

      • 我直到的hive的文件格式有textFile,sequenceFile,ORC,Parque;其中textFile为hive的默认存储格式,它和sequenceFile一样都是基于行存储的,ORC,Parquet是基于列存储的。sequenceFile,ORC,Parque文件都是以二进制的方式存储的。
    • 合适的压缩格式:减少了IO读写和网络传输的数据量,比如常用的LZO(可切片)和snappy

  • 语法优化:

    • 单表查询优化:

      • 列裁剪和分区裁剪:如果select * 或者不指定分区,全列扫描和全表扫描效率都很低(公司规定了必须指定分区,select * 没有明确规定)
      • group by优化:
        • 开启map端聚合
        • 开启负载均衡:这样生成的查询计划会有两个MR job,一个是局部聚合(加随机数),另一个是全局聚合(删随机数)
      • SQL写成多重模式:有多条SQL重复扫描一张表,那么我们可以写成from 表 select … select
    • 多表优化查询:

      • CBO优化:选择代价最小的执行计划:自动优化HQL中多个Join的顺序,并选择合适的Join算法
        • set hive.cbo.enable=true(默认开启)
      • 谓词下推:将SQL语句中的where谓词逻辑都尽可能提前执行,减少下游处理的数据量。
        • hive.optimize.ppd=true(默认开启)
      • MapJoin:将join双方比较小的表直接分发到各个Map进程的内存中,在Map进程中进行join操作,这样就不用进行Reduce,从而提高了速度
        • set hive.auto.convert.join=true(默认开启)
        • set hive.mapjoin.smalltable.filesize=25000000(默认25M以下是小表)
      • SMB Join:分桶join,大表转换为很多小表,然后分别进行join,最后union到一起
  • job优化:

    • map优化
      • 复杂文件增加map数
      • 小文件合并
      • map端聚合
      • 推测执行
    • reduce优化
      • 合理设置reduce:
        • 为什么不是reduce的数量越多越好?
          • 过多的启动和初始化reduce也会消耗时间和资源
          • 另外,有多少个reduce,就会有多少个输出文件,如果生成的很多个小文件,那么如果这些小文件作为下一个任务的输入,则也会出现小文件过多的问题;
      • 推测执行
    • 任务整体优化:
      • fetch抓取:Hive中对某些情况的查询可以不必使用MapReduce计算【全局查找、字段查找、limit查找】hive.fetch.task.conversion=more
      • 小数据集启用本地模式hive.exec.mode.local.auto=true
      • 多个阶段并行执行 set hive.exec.parallel=true
      • JVM重用:针对小文件过多的时候使用

15. 常用函数的补充

  • NVL(value, default):如果value为null,就返回default,否则返回value
  • IF(expr, value1, value2):如果expr为true,返回value1,否则返回value2
  • concat_WS(seperator, str1, str2, …):参数可以是字符串,也可以是数组
  • substring(value, start, len):字符串索引是从1开始,我们要截取value中第start字符开始len长度的字符串
  • 日期函数:
  • 行列转换: