HiveSQL常用(下篇:使用技巧与优化)

2022-07-22,,,,

很高兴遇到你~

 

hivesql使用技巧优化

sql执行顺序:from->join->where->group by->having->select->order by->limit 

  • distinct去重与count
--distinct去重时,如果存在null,结果会异常,hive不会将null值归为一个值处理,此时需要给null进行转换
select distinct nvl(column1,''),nvl(column2,0) from t;

--count(*)、count(1)对所有行进行统计,包括null行,count(column_name)只对该列中非null的进行统计

--hive中要避免使用count(distinct),它无法进行聚合操作,只在一个reduce上完成,容易出现性能瓶颈甚至oom内存溢出,使用group by来替代
--count distinct
select col1,count(distinct id) as did
from t
group by col1;
--使用group by优化替代
select col1,count(id) as did
from(select col1,id from t group by col1,id) as temp
group by col1;

 

  • subquerys子查询&exists/in&left semi join
--subquerys子查询:hive只支持from和where后的子查询
--如果子查询中包含null值,不能使用not in(not in会报错,in不会)
--不推荐使用in/not in,可使用exists/not exists替代,支持子查询中的多值匹配 --not exists和left join可以有等价写法 --not exists select a,b from t1 where not exists(select 1 from t2 where t1.a=t2.a and t1.b=t2.b); --等价not exists的left join写法 select t1.a,t2.b from t1 left join t2 on (t1.a=t2.a and t1.b=t2.b) where t2.a is null; --left semi join 替代 in和exists,效率更高 --left semi join(左半连接)是in/exists子句查询的一种更高效的实现 --left semi join 的限制是:join 子句中右边的表只能在on 子句中设置过滤条件,在where 子句、select 子句或其他地方过滤都不行 --left semi join 只会显示出左边表的字段,left semi join会掉右表中重复的记录,不会因为右表重复key join出多条 --in/exists select a.key, a.value from a where a.key in (select b.key from b);
--left semi join替代in/exists select a.key, a.val from a left semi join b on (a.key = b.key)

in和exists的选取:使用exists时,内表会对外表进行循环查询匹配,它不在乎后面内表子查询的返回值,只在乎有没有返回值,存在返回值,则条件为true,该条数据匹配成功,加入查询结果集中,如果没有返回值,条件为假,丢弃该条数据。in用法与exists一致,查询结果和解析计划一致,in在hql中只会查询一次,然后把结果集存在临时文件中,然后再与外层查询sql进行匹配。如果子查询得出的结果集记录较少,主查询中的表较大时使用in,反之外层的主查询记录数较少,子查询中的表大,优先使用exists。其实区分in和exists主要是造成了驱动顺序的改变,如果是exists,那么以外层表为驱动表,先被访问,如果是in,那么先执行子查询。子查询的表大的时候,使用exists可以有效减少总的循环次数来提升速度,当外查询的表大的时候,使用in可以有效减少对外查询表循环遍历来提升速度。

select a.id,a.name from t1 as a where exists(select b.id from t2 as b where a.id=b.id);
select a.id,a.name from t1 as a where a.id in (select b.id from t2 as b);

 

  • sort by&distribute by&cluster by&order by
/******************************************************************
  数据量较小order by即可,hive中尽量不要使用order by,除非非常确定结果集非常小;
  如果在strict模式下使用order by语句,那么必须要在语句中加上limit关键字,因为执行order by只启动单个reduce,如果排序的结果集过大,那么执行时间会很久;
  实际场景中一般先使用sort by再使用order by效率更高一些,使用distribute和sort进行分组排序,sort by+order by,sort by过程可以设置reducer个数(n),order by过程用n个reduce的输出文件进行一次全排序,得到最终结果;
  distribute指定map输出结果是如何分配的,上句中相同的id会被分配到同一个reduce上去处理,然后再通过sort by对各个reduce上的id进行排序(被distribute by设定的字段为key,数据会被hash分发到不同的reducer机器上,然后sort by会对同一个reducer机器上的每组数据进行局部排序)。
******************************************************************/set hive.mapred.mode=nonstrict; (default value / 默认值)
set hive.mapred.mode=strict;

--sort by&distribute by
--sort by只能保证在单个reduce内有序
select * from baidu_click distribute by product_line sort by click desc;
select * from t distribute by id sort by id;

--cluster by(distribute by + sort by替代方案)
--当distribute by和sort by的字段完全一致时,等价于cluster by,但cluster by排序只能是升序排序,不能指定排序规则为asc或者desc
--cluster by 和 distribute by 是很相似的,最大的不同是, cluster by 里含有一个分桶的方法
select * from emp cluster by deptno;
select * from emp distribute by deptno sort by deptno; 

--常见两种高效的排序实现
--可先通过一个group by的子查询来取一个小的结果集,然后再对这个结果集进行全局排序
select * from (
select id,count(id) as cnt
from t
group by id) as temp
order by temp.cnt;

--高效实现top排序
--先取出各个结果集的top n,再取出全局的top n
select a.id,salary
from (select id,salary from t1 distribute by sort by salary desc limit 10) as temp
order by temp.salary limit 10;

 

limit抽样

--使用limit时限制数据,不会进行全盘扫描,而是根据限制的数据量进行抽样,带有reduce的limit会产生不同结果
--设置参数优化(建议使用时手动开启)
set hive.limit.optimize.enable=true;  --默认false
set hive.limit.row.max.size=xxx;  --最大抽样数量 默认10万
set hive.limit.optimize.limit.file=xxx;  --最大抽样文件数量 默认10 

 

join on和where的区别

--join on和where在做条件筛选时,on会显示所有满足条件和不满足条件的数据,而where只显示满足条件的数据
select t1.a,t2.a
from t1 
left join on t1.key=t2.key and t2.a>10;  --t1.a 非>10的数据仍会显示

select t1.a,t2.a
from t1 
left join on t1.key=t2.key 
where t2.a>10;  --过滤掉前面join后的结果集,只显示t1.a>10的数据

 

join优化

在做join连接时,倾向于将小表放入内存中,然后对大表进行map操作,此时join只发生在map阶段,即当扫描一个大表时,同时会去小表中查找与之匹配的数据进行连接,而无任何shuffle过程(shuffle往往带来大量的内存、磁盘、数据传输等资源消耗),从而提高执行效率。

--设置mapjoin参数
--开启方式(如果开启如下设置,小于阈值大小的表将被自动认为是小表而加载到内存(分布式缓存)中去)
--建议在使用join时都开启此功能
set hive.auto.convert.join=true
set hive.mapjoin.smalltable.filesize=xxxx  --默认是25m

多表join的连接顺序:多表连接中join连接顺序,会转换为多个mr任务,每一个mr任务在hive中成为join阶段(stage),在每一个stage,按照join顺序最后一个表尽量是最大的表,因为join前一阶段生成的数据会存在于reduce的buffer中,通过stream最后面的表,直接从reduce的buffer中读取已经缓冲的中间结果数据(中间结果数据可能是join顺序中,前面表连接的结果key,数据量相对较小,内存开销就越小),这样在与后面的大表进行连接时,只要从buffer中读取缓存的key,与大表的指定key进行连接,速度会更快,也避免可能的内存缓冲区溢出。

--多个表a、b、c、d表join时,数据量a<b<c<d
select a.val,b.val,c.val,d.val
from a 
join b on a.key=b.key
join c on b.key=c.key
join d on c.key=d.key;

join key为null容易导致的错误:有别于其它传统关系型数据库,如在mysql中,join时,join key遇到null值会自动被忽略,但是在hive中,作为join key的字段比较关联,null=null是有意义的,且返回ture,但是这个情景是没有任何实际意义的,且在null值比例很大的情况下,会导致严重的数据倾斜,所以在hive中一般需要手动过滤掉null值。

select t1.id,count(t1.id)
 from t1 join t2
  on (t1.id=t2.id and t1.id is not null and t2.id is not null)
group by t1.id;

 

hivesql使用注意项

  1. 创建表和删除表使用if not exists/if exists防止异常;

  2. 分区字段不能出现在建表中,只能出现在partition by中;

  3. 使用具体列名避免使用select *;

  4. where 条件过滤时,!=、<>都会将null值过滤掉,导致实际结果集变小,如果需要保留null值:where (col1 <> 'value' or col1 is null);

  5. group by时,select的列别名不能被group by解析,group by后不能使用别名,因为hive执行解析严格按照sql执行顺序,先group by,后select;

  6. hive不支持update操作,只能drop再insert;

  7. hive创建视图和其它数据库创建视图无异;

  8. hive int与string类型,null底层默认存储为\n,查询显示为null,导出文件会以存储格式导出,需要注意。若导出为null,存储的字符串就是null字符串而非null值;sql中null代表空值, 值得警惕的是, 在hiveql中string类型的字段若是空(empty)字符串, 即长度为0, 那么对它进行is null的判断结果是false;

  9. 分号是sql语句结束标记,在hiveql中也是,但是在hivesql中,对分号的识别没有那么智慧,在dbeaver sql ide中也会出现因为加了;导致sql报错的情况。另外字符';'使用需要转义,如:select concat(key,concat(';',key)); 在hive中会报错,应使用分号的八进制的ascii码进行转义,应写成:select concat(key,concat('\073',key));

  10. hive支持嵌入mapreduce程序,来处理复杂的逻辑,但一般不使用,为了维护方便,类似桶表一般也不使用;

  11. 如何查看hive的属性设置情况,如:set hive.mapred.mode; --hive.mapred.mode=nonstrict,要注意的是strict模式也会限制分区表的查询,解决方案是必须指定分区;

  12. hive的join on条件只支持等值连接(如:t1.key=t2.key),where条件则无限制。hive从2.2.0版本开始,join on也支持复杂表达式,包括非等值连接;

  13. 窗口函数row_number()使用不当问题,在使用row_number()时,over()里面的分组以及排序晚于where、group by、order by执行。partition by用于给结果集分组,在hive中使用row_number()窗口函数时,如果使用了partition参数,需要特别注意partition by的key如果太多,超过了集群的限制,则会报错。如:select empno,deptno,salary,row_number(partition by deptno order by salary desc) rank from employee;

  14. create table as select * from other_table 容易引起风险问题,如果源表某个字段修改了字段类型,如col1本是int型,修改为了string,那么通过这种方式创建的表该字段还是会被推断为int,这就存在了风险隐患,建议使用:drop table if exists table_name; create table t_new as select * from table_old where 1=2;或者:create table t_new like t_old;或者使用标准表创建再插入数据。 

《HiveSQL常用(下篇:使用技巧与优化).doc》

下载本文的Word格式文档,以方便收藏与打印。