Apache Hive基础知识

你想要什么?你在做什么?它们一样吗?你今天比昨天更好吗?


我的hive学习笔记

一、简介

Apache Hive是一个提供访问HDFS上存储数据的SQL接口的数据仓库。适合存储数据不会频繁变化,且不需要快速响应给出结果的场景。Hive2.1.0暂不支持记录级别的更新、删除。

二、架构

在这里插入图片描述

JobTracker负责分配任务和资源管理
TaskTracker负责具体执行
Driver负责SQL编译、优化、执行
MetaStore存储元数据

1 Hive语句的执行过程

在这里插入图片描述
Hive 这一侧,总共有五个组件:

  • UI:用户界面。可看作我们提交SQL语句的命令行界面。

  • DRIVER:驱动程序。接收查询的组件。该组件实现了会话句柄的概念。

  • COMPILER:编译器。负责将 SQL 转化为平台可执行的执行计划。对不同的查询块和查询表达式进行语义分析,并最终借助表和从 metastore 查找的分区元数据来生成执行计划。

  • METASTORE:元数据库。存储 Hive 中各种表和分区的所有结构信息。

  • EXECUTION ENGINE:执行引擎。负责提交 COMPILER 阶段编译好的执行计划到不同的平台上。

图里流程详细情况

  1. 步骤1:UI 调用 DRIVER 的接口;

  2. 步骤2:DRIVER 为查询创建会话句柄,并将查询发送到 COMPILER(编译器)生成执行计划;

  3. 步骤3和4:编译器从元数据存储中获取本次查询所需要的元数据,该元数据用于对查询树中的表达式进行类型检查,以及基于查询谓词修建分区;

  4. 步骤5:编译器生成的计划是分阶段的DAG,每个阶段要么是 map/reduce 作业,要么是一个元数据或者HDFS上的操作。将生成的计划发给 DRIVER。

  5. 如果是 map/reduce 作业,该计划包括 map operator trees 和一个 reduce operator tree,执行引擎将会把这些作业发送给 MapReduce :

  6. 步骤6、6.1、6.2和6.3:执行引擎将这些阶段提交给适当的组件。在每个 task(mapper/reducer) 中,从HDFS文件中读取与表或中间输出相关联的数据,并通过相关算子树传递这些数据。最终这些数据通过序列化器写入到一个临时HDFS文件中(如果不需要 reduce 阶段,则在 map 中操作)。临时文件用于向计划中后面的 map/reduce 阶段提供数据。

  7. 步骤7、8和9:最终的临时文件将移动到表的位置,确保不读取脏数据(文件重命名在HDFS中是原子操作)。对于用户的查询,临时文件的内容由执行引擎直接从HDFS读取,然后通过Driver发送到UI。

2 元数据的三种模式

	1 单用户模式(不建议使用此模式)
		此模式连接到一个In-memory的数据库Derby,同一时间只允许一个用户连接
	2 多用户模式(推荐使用此模式)
		通过网络连接远程数据库,允许多个用户连接
	3 远程服务模式
		多了一层封装,可以提供用户通过Thrift协议调用服务来访问元数据

三、客户端命令行工具

Hive Cli

Hive Cli是一个客户端命令行界面,是和Hive交互的最常用的方式,使用Cli,用户可以建表、导入数据、查询等等。

注意:Hive1.0.0 后 Hive Cli不推荐使用,推荐的是Beeline Cli

案例:·

# 切换到hdfs用户
su hdfs
# 启动Hive Cli
hive
# 查看所有数据库
show Databases;
# 使用default数据库
use default;
# 查看所有的表
show tables;

Hive Cli 官方参考文档:
https://cwiki.apache.org/confluence/display/Hive/GettingStarted#GettingStarted-RunningHiveCLI

Beeline Cli

Hive 1.0.0后推荐使用Beeline Cli

案例:

# 切换到hdfs用户
su hdfs
# 启动beeline
beeline 
# 连接指定ip,port和用户名密码的HiveServer2
!connect jdbc:hive2://localhost:10000 username passward
# 查看所有数据库
show databases;
# 使用default;
use default;
# 查看所有表
show tables;

Beeline 官方参考文档:
https://cwiki.apache.org/confluence/display/Hive/HiveServer2+Clients#HiveServer2Clients-Beeline%E2%80%93NewCommandLineShell

四、数据类型

基本数据类型

类型描述示例
tinyint1个字节有符号整数1
smallint2个字节有符号整数2
int4个字节有符号整数3
bigint8个字节有符号整数4
flout4个字节单精度浮点数1.0
double8个字节双精度浮点数1.0
boolean布尔值true
string字符串,无长度限制“hello”

复合数据类型

类型描述示例
ARRAY有序列表,字段类型必须相同array(1,2,3)
MAP无序key-value映射,key的类型必须为基本类型,value可以是任意类型。同一个MAP中key的类型必须相同,value的类型也必须相同map(“a”:1,“b”:2,“c”:3)
struct可以包含不同数据类型的元素,多个字段为一组struct(“zhangsan”,23,3000)

数据类型 官方参考文档:
https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Types#LanguageManualTypes-Overview

五、语法

这里只详细记录常用的,冷门操作可以看官方文档

1 DDL

数据库定义语言

DDL 官方文档:
https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-Overview

  • CREATE DATABASE/SCHEMA, TABLE, VIEW, FUNCTION, INDEX
  • DROP DATABASE/SCHEMA, TABLE, VIEW, INDEX
  • TRUNCATE TABLE
  • ALTER DATABASE/SCHEMA, TABLE, VIEW
  • MSCK REPAIR TABLE (or ALTER TABLE RECOVER PARTITIONS)
  • SHOW DATABASES/SCHEMAS, TABLES, TBLPROPERTIES, VIEWS, PARTITIONS, FUNCTIONS, INDEX[ES], COLUMNS, CREATE TABLE
  • DESCRIBE DATABASE/SCHEMA, table_name, view_name, materialized_view_name

1.1 Create/Drop/Alter/Use Database

数据库的创建、删除、修改、使用操作

1.1.1 Create Database

创建数据库

语法:

CREATE (DATABASE|SCHEMA) [IF NOT EXISTS] database_name
  [COMMENT database_comment]
  [LOCATION hdfs_path]
  [MANAGEDLOCATION hdfs_path]
  [WITH DBPROPERTIES (property_name=property_value, ...)];
  • DATABASE和SCHEMA是一个意思,类似Mysql里的Database
  • COMMENT 备注,添加备注后,查看库的时信息可以看到
  • LOCATION 默认库的路径,创建内部表后,内部表的默认父路径
  • MANAGEDLOCATION 默认的管理库的路径,创建外部表后,外部表的默认父路径
  • DBPROPERTIES 自定义设置一些属性,比如可以指定username=zhangsan,create_time=2021-12-12 12:12:12

示例:

create database if not exists test_db_1 
comment "test database"  
with dbproperties (create_user=zhangsan);
1.1.2 Use Database

使用数据库

语法:

USE database_name;

示例:

use test_db_1 ;
1.1.3 Drop Database

删除数据库

语法:

DROP (DATABASE|SCHEMA) [IF EXISTS] database_name [RESTRICT|CASCADE];
  • CASCADE 假如数据库里有表,默认删除时会提示无法删除,此时加上CASCADE 即可强制删除(慎用)

案例:

# 强制删除数据库,即使有表存在也会一起删除
drop database if exists test_db_1 cascade;

1.2 Create/Drop/Truncate Table

表的创建、删除、截断操作

1.2.1 Create Table

语法:

-- 语法1-直接建表
CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name    -- (Note: TEMPORARY available in Hive 0.14.0 and later)
  [(col_name data_type [column_constraint_specification] [COMMENT col_comment], ... [constraint_specification])]
  [COMMENT table_comment]
  [PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)]
  [CLUSTERED BY (col_name, col_name, ...) [SORTED BY (col_name [ASC|DESC], ...)] INTO num_buckets BUCKETS]
  [SKEWED BY (col_name, col_name, ...)                  -- (Note: Available in Hive 0.10.0 and later)]
     ON ((col_value, col_value, ...), (col_value, col_value, ...), ...)
     [STORED AS DIRECTORIES]
  [
   [ROW FORMAT row_format] 
   [STORED AS file_format]
     | STORED BY 'storage.handler.class.name' [WITH SERDEPROPERTIES (...)]  -- (Note: Available in Hive 0.6.0 and later)
  ]
  [LOCATION hdfs_path]
  [TBLPROPERTIES (property_name=property_value, ...)]   -- (Note: Available in Hive 0.6.0 and later)
  [AS select_statement];   -- (Note: Available in Hive 0.5.0 and later; not supported for external tables)

-- 语法2-使用现有表建表
CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name
  LIKE existing_table_or_view_name
  [LOCATION hdfs_path];

关键词解释

  • TEMPORARY 建表时添加这个关键字建好的表是临时表,当前会话退出后表就会被删除,用的较少
  • EXTERNAL 建表时指定external的表是外部表,什么是外部表下面有介绍
  • IF NOT EXISTS 如果表不存在则创建,存在则正常退出
  • db_name. 指定要创建的表位于哪个数据库
  • table_name 指定表名
  • col_name data_type COMMENT col_comment 字段名和字段对应的类型,COMMENT的意思是申明字段的备注,备注内容为col_comment;可以有多个
  • COMMENT table_comment 申明表的备注,备注内容为table_comment
  • PARTITIONED BY (col_name data_type [COMMENT col_comment], …) 指定根据哪些字段分区,col_name data_type COMMENT col_comment 指定分区字段名和字段类型、备注和备注内容;可以有多个
  • CLUSTERED BY (col_name, col_name, …) [SORTED BY (col_name [ASC|DESC], …)] INTO num_buckets BUCKETS
    CLUSTERED BY申明分桶,根据类型为字段名为col_name等一个或多个字段分桶,SORTED BY根据列名为col_name的字段进行排序,asc是顺序,desc是倒序,INTO num_buckets BUCKETS指定分几个桶,桶的数量是num_buckets
  • SKEWED BY (col_name, col_name, …) 指定数据倾斜列,可以提高有数据倾斜列时的查询性能
  • ON ((col_value, col_value, …) 指定具体的倾斜列的倾斜的值
  • STORED AS DIRECTORIES 指定使用列表桶,为倾斜的字段的值创建子目录,查询时提高性能
  • ROW FORMAT row_format 指定字段、array、map、行按照什么规则切分,切分规则如下
    row_format
    : DELIMITED [FIELDS TERMINATED BY char [ESCAPED BY char]] [COLLECTION ITEMS TERMINATED BY char]
    [MAP KEYS TERMINATED BY char] [LINES TERMINATED BY char]
    [NULL DEFINED AS char] – (Note: Available in Hive 0.13 and later)
    | SERDE serde_name [WITH SERDEPROPERTIES (property_name=property_value, property_name=property_value, …)]
  • STORED AS file_format 指定文件格式,支持TEXTFILE(默认)、SEQUENCEFILE、RCFILE、ORC、PARQUET、AVRO、JSONFILE(Hive4.0.0后支持的类型,其他类型都是1.0.0版本之前就支持的类型)
  • LOCATION hdfs_path 指定表在HDFS上的存储路径
  • TBLPROPERTIES (property_name=property_value, …) 指定表的自定义属性,既可以修改预先定义好的属性也可以自定义添加新属性,这里类似数据库的自定义属性
  • AS select_statement 以查询结果作为表结构

Hive表有四种,内部表,外部表,分区表,分桶表

  • 内部表是不指定external和temporary时默认的建表方式,这种方式创建的表被删除时,数据也会被一起删除,表定义和数据强关联
  • 外部表是指定external的表,这种方式创建的表被删除时,数据不会被一起删除,表定义和数据弱关联
  • 分区表就是有分区的表,分区就是把数据安装某个或多个字段分别存在不同的目录下。在查询时可以根据分区筛选数据,减少读取的数据量
  • 分桶表

案例:

-- 内部表
create table if not exists default.test_tb_1(
	user_id int "user's id",
	user_name string "user's name"
);
-- 外部表
CREATE EXTERNAL TABLE page_view(viewTime INT, userid BIGINT,
     page_url STRING, referrer_url STRING,
     ip STRING COMMENT 'IP Address of the User',
     country STRING COMMENT 'country of origination')
 COMMENT 'This is the staging page view table'
 ROW FORMAT DELIMITED FIELDS TERMINATED BY '\054'
 STORED AS TEXTFILE
 LOCATION '<hdfs_location>';
-- 分区表
create external table if not exists default.test_tb_2(
	user_id int "user's id",
	user_name string "user's name"
)partitioned by (create_date string,org_num int);
-- 分桶表
CREATE TABLE page_view(viewTime INT, userid BIGINT,
     page_url STRING, referrer_url STRING,
     ip STRING COMMENT 'IP Address of the User')
 COMMENT 'This is the page view table'
 PARTITIONED BY(dt STRING, country STRING)
 CLUSTERED BY(userid) SORTED BY(viewTime) INTO 32 BUCKETS
 ROW FORMAT DELIMITED
   FIELDS TERMINATED BY '\001'
   COLLECTION ITEMS TERMINATED BY '\002'
   MAP KEYS TERMINATED BY '\003'
 STORED AS SEQUENCEFILE;
1.2.2 Drop Table

删除表

语法:

DROP TABLE [IF EXISTS] table_name [PURGE]; 

关键字解释

  • PURGE 跳过回收站,在不指定purge的默认情况下,数据会进入HDFS的.Trash/Current路径下同时表定义会被完全删除,误删后可以通过此项找回。但指定purge后,数据将被跳过回收站直接删除(慎用)

案例:

drop table if exists default.test_tb_1;
1.2.3 Truncate Table

清空表,只能清空内部表或内部表分区的数据
语法:

TRUNCATE TABLE table_name [PARTITION partition_spec];

关键字解释:

  • table_name 要清空的表名
  • PARTITION partition_spec 要清空具体分区

案例:

truncate table default.test_tb_1;
1.2.3 Alter Table/Partition/Column

修改表、分区、列
内容较多,常用的不多,建议直接查看官方文档

此部分内容的官方文档:
https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-AlterTable

2 DML

数据处理语言

2.1 将数据导入表

注意

  • 多个分区字段是有先后顺序的;
  • 动态插入分区表时需要开启动态分区(set hive.exec.dynamic.partition=true; set hive.exec.dynamic.partition.mode=nonstrict;);
  • 导入分区表或动态插入分区表时,分区字段必须按顺序排到所有非分区字段后
2.1.1 Loading files into tables

将文件导入到表

语法:

LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE] INTO TABLE tablename [PARTITION (partcol1=val1, partcol2=val2 ...)]

关键词意思

  • LOCAL 指定文件存放在本地文件系统,不指定LOCAL默认为HDFS文件系统;从本地导入时是复制,导入表后原有数据仍旧存在;从HDFS导入时,是移动,导入后原路径数据会消失;
  • filepath 文件的路径
  • OVERWRITE 指定清空表再导入新数据,默认为append(追加)
  • tablename 要导入的表
  • PARTITION (partcol1=val1, partcol2=val2 …) 如果上面的表是分区表,那么需要指定要导入的分区;有多个分区需要指定多个分区

案例:

# 导入本地的数据到非分区表,追加导入
LOAD DATA LOCAL INPATH '/home/admin/test/test.txt' INTO TABLE test_1;
# 导入本地的数据到分区表的指定分区,追加导入
LOAD DATA LOCAL INPATH '/home/admin/test/test.txt' INTO TABLE test_1 PARTITIO(pt='20200101');

# 导入HDFS的数据到非分区表,覆盖原有数据
LOAD DATA INPATH '/home/admin/test/test.txt' OVERWRITE  INTO TABLE test_2;
# 导入HDFS的数据到分区表的指定分区,覆盖原有数据
LOAD DATA INPATH '/home/admin/test/test.txt' OVERWRITE INTO TABLE test_1 PARTITIO(pt='20200101');
2.1.2 Inserting data into Hive Tables from queries

从查询插入数据到hive表

语法:

# 标准语法
	#覆盖原有数据
	INSERT OVERWRITE TABLE tablename1 [PARTITION (partcol1=val1, partcol2=val2 ...) [IF NOT EXISTS]] select_statement1 FROM from_statement;
	# 追加插入
	INSERT INTO TABLE tablename1 [PARTITION (partcol1=val1, partcol2=val2 ...)] select_statement1 FROM from_statement;

# 动态分区插入
INSERT OVERWRITE|INTO TABLE tablename PARTITION (partcol1[=val1], partcol2[=val2] ...) select_statement FROM from_statement;

案例:

# 插入非分区表,覆盖原有数据
insert overwrite table tablename1 select a, b, c from test_1;
# 插入非分区表,覆盖原有数据
insert overwrite table tablename1 select a, b, c from test_1;
# 插入分区表,以追加的方式
insert into table tablename1 partition (create_date='20210101',org='1') select a, b, c from test_1;

# 一个静态分区,一个动态分区,插入分区表
insert overwrite table page_view partition (dt='2008-06-08', country) select pvs.user_id, pvs.user_name from page_view_stg pvs;
# 动态插入分区表,2个动态分区,不包括静态分区
insert overwrite table test_1(create_date, org) select user_id, user_name,create_date,org from tablename1;

动态分区需要注意调整的参数

参数名默认值意思
hive.exec.dynamic.partitiontrue设置为true,表示开启动态分区
hive.exec.dynamic.partition.modestrict当处于strict模式时,用户至少需要指定一个静态分区;在nonstrict 模式时,允许分区都是动态分区
hive.exec.max.dynamic.partitions.pernode100每个mapper/reducer被允许创建的动态分区数的最大值
hive.exec.max.dynamic.partitions1000允许创建的动态分区数的最大值
hive.exec.max.created.files100000hive任务的mapper/reducer创建的文件数的个数
hive.error.on.empty.partitionfalse动态分区插入产生空结果时是否抛出异常
2.1.3 Writing data into the filesystem from queries

将查询出的数据导出到文件系统

语法:

INSERT OVERWRITE [LOCAL] DIRECTORY directory1
  [ROW FORMAT row_format] [STORED AS file_format] (Note: Only available starting with Hive 0.11.0)
  SELECT ... FROM ...

row_format
  : DELIMITED [FIELDS TERMINATED BY char [ESCAPED BY char]] [COLLECTION ITEMS TERMINATED BY char]
        [MAP KEYS TERMINATED BY char] [LINES TERMINATED BY char]
        [NULL DEFINED AS char] (Note: Only available starting with Hive 0.13)

案例:

# 将表数据导出到本地文件系统,分割符为逗号
insert OVERWRITE LOCAL DIRECTORY '/home/hadoop/local_test_output' ROW FORMAT DELIMITED FIELDS TERMINATED by ',' select * from test_1;
# 将表数据导出到HDFS文件系统,分隔符为逗号
insert OVERWRITE DIRECTORY '/home/hadoop/hdfs_test_output' ROW FORMAT DELIMITED FIELDS TERMINATED by ',' select * from test_1;

注意导出到文件系统时,启动用户要有输出目录的写权限。

DML 官方文档:
https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DML

3 DQL

数据查询语言

语法:

[WITH CommonTableExpression (, CommonTableExpression)*]    (Note: Only available starting with Hive 0.13.0)
SELECT [ALL | DISTINCT] select_expr, select_expr, ...
  FROM table_reference
  [WHERE where_condition]
  [GROUP BY col_list]
  [HAVING having_condition]
  [ORDER BY col_list]
  [CLUSTER BY col_list
    | [DISTRIBUTE BY col_list] [SORT BY col_list]
  ]
 [LIMIT [offset,] rows]

关键字解释:

  • WITH CommonTableExpression (, CommonTableExpression)*
    定义CTE(公共表表达式),定义好后,一个查询里可以多次引用定义好的公共表
  • SELECT [ALL | DISTINCT] select_expr, select_expr, …
    DISTINCT指定时,查询返回去重后的结果,不指定默认为ALL,返回所有数据
    select_expr 查询的列或者表达式,例如user_id,max(user_id)
  • FROM table_reference
    table_reference 表名
  • WHERE where_condition
    where_condition 筛选条件,对select到的列的值进行筛选,保留满足条件的
  • GROUP BY col_list
    col_list 分组依据的列
  • HAVING having_condition
    having_condition 分组后,组内数据的筛选条件,保留满足条件的
  • ORDER BY col_list
    col_list 根据指定的字段排序,全局排序;order by 是在一个reduce里执行,所以数据量大时,速度很慢,可以通过嵌套排序加快速度
  • CLUSTER BY col_list | [DISTRIBUTE BY col_list] [SORT BY col_list
    CLUSTER BY col_list 根据指定的列排序,全局排序,只能降序
    DISTRIBUTE BY col_list 根据指定的字段分区,同一个字段的同一个值落在一个分区里
    SORT BY col_list 每个reducer里排序,非全局排序
  • LIMIT [offset,] rows
    限制返回的条数,offset表示返回的数据从第几行开始,rows表示返回几行

案例:

# 定义公共表,查询公共表
with 
t1 as select * from test_1
select * from t1;

# 查询
select org,sum(age)
from t_user
where user_id > 1
group by org
having avg(age) > 35
order by org;

3.1 语句执行顺序

Hive SQL执行顺序

-- 正常SQL顺序
selectfromwheregroup byhavingorder bylimit-- 真正执行顺序
fromjoinwhereselectgroup byhavingorder bylimit

我的理解:

  1. from 找到从哪个或哪几个表找数据
  2. where 根据where条件留下符合条件的数据,这个是过滤不需要的行
  3. select 留下要查询的字段,这个是过滤不需要的列
  4. group by 根据哪些字段分组
  5. having 组内过滤
  6. order by 排序
  7. 限制返回行数

3.2 操作符和用户自定义函数

此部分内容较多,且官方文档就很容易理解,直接看官方文档即可

官方文档:
https://cwiki.apache.org/confluence/display/Hive/LanguageManual+UDF

4 开窗函数、分析函数

4.1 WINDOW子句

用于指定窗口范围

PRECEDING # 往前
FOLLOWING # 往后
CURRENT ROW # 当前行
UNBOUNDED # 起点(一般结合PRECEDING,FOLLOWING使用)
UNBOUNDED PRECEDING # 表示该窗口最前面的行(起点)
UNBOUNDED FOLLOWING # 表示该窗口最后面的行(终点)

示例:

ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW #(表示从起点到当前行)
ROWS BETWEEN 2 PRECEDING AND 1 FOLLOWING #(表示往前2行到往后1行)
ROWS BETWEEN 2 PRECEDING AND 1 CURRENT ROW #(表示往前2行到当前行)
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING #(表示当前行到终点)

不支持window子句的函数

Lead、Lag、Rank、NTile、DenseRank、CumeDist、PercentRank

4.2 窗口函数

建表语句

create table test_user_login(
  user_name string, 
  login_date string, 
  pv long
) row format delimited fields terminated by ',';

测试数据

user_namelogin_datepv
zhangsan2021-01-013
zhangsan2021-01-024
lisi2021-01-015
lisi2021-01-026
wangwu2021-01-012
4.2.1 LEAD

LEAD(col, n, DEFAULT) 用于查询窗口内往下第n行值

  • 第一个参数是列名
  • 第二个参数为往下第n行(可选,默认为1),
  • 第三个参数为默认值(当往下第n行为NULL时候,取默认值,如不指定,则为NULL)

案例

# 查询用户当前行的下一次登录的pv
select 
  user_name, 
  login_date, 
  pv, 
  LEAD(pv, 1, 0) over (
    partition by user_name 
    order by 
      login_date
  ) as lead_1_pv 
from 
  default.test_user_login;

查询结果

user_namelogin_datepvlead_1_pv
zhangsan2021-01-0134
zhangsan2021-01-0240
lisi2021-01-0156
lisi2021-01-0260
wangwu2021-01-0120
4.2.2 LAG

LAG(col, n, DEFAULT) 用于查询窗口内往上第n行值

  • 第一个参数col是列名
  • 第二个参数为往上第n行(可选,默认为1)
  • 第三个参数为默认值(当往上第n行为NULL时候,取默认值,如不指定,则为NULL)

案例

# 查询用户当前行的上一次登录的pv
select 
  user_name, 
  login_date, 
  pv, 
  LAG(pv, 1, 0) over (
    partition by user_name 
    order by 
      login_date
  ) as lead_1_pv 
from 
  default.test_user_login;

查询结果

user_namelogin_datepvlead_1_pv
zhangsan2021-01-0130
zhangsan2021-01-0243
lisi2021-01-0150
lisi2021-01-0265
wangwu2021-01-0120
4.2.3 FIRST_VALUE

FIRST_VALUE(col, flag)取分组内排序后,截止到当前行,第一个值

  • 第一个参数是您想要第一个值的列
  • 第二个(可选)参数默认情况是false,不跳过空值。如果设置为 true 则跳过空值

案例

# 查询用户第一次登录日期和pv
select 
  user_name, 
  login_date, 
  FIRST_VALUE(login_date) over (
    partition by user_name 
    order by 
      login_date
  ) as first_login_date, 
  ROW_NUMBER() over (
    partition by user_name 
    order by 
      login_date
  ) AS rn, 
  FIRST_VALUE(pv) over (
    partition by user_name 
    order by 
      login_date
  ) as first_pv 
from 
  default.test_user_login;

查询结果

user_namelogin_datefirst_login_datepvrnfirst_pv
zhangsan2021-01-012021-01-01313
zhangsan2021-01-022021-01-01423
lisi2021-01-012021-01-01515
lisi2021-01-022021-01-01625
wangwu2021-01-012021-01-01212

加window子句案例

# 查询用户第一次登录日期和pv
select 
  user_name, 
  login_date, 
  FIRST_VALUE(login_date) over (
    partition by user_name 
    order by 
      login_date
  ) as first_login_date, 
  ROW_NUMBER() over (
    partition by user_name 
    order by 
      login_date
  ) AS rn, 
  FIRST_VALUE(pv) over (
    partition by user_name 
    order by 
      login_date
  ) as first_pv, 
  FIRST_VALUE(pv) over (
    partition by user_name 
    order by 
      login_date rows between unbounded preceding 
      and current row
  ) as window_first_pv 
from 
  default.test_user_login;

查询结果

user_namelogin_datefirst_login_datepvrnfirst_pvwindow_first_pv
zhangsan2021-01-012021-01-013133
zhangsan2021-01-022021-01-014233
lisi2021-01-012021-01-015155
lisi2021-01-022021-01-016255
wangwu2021-01-012021-01-012122
4.2.3 LAST_VALUE

FIRST_VALUE(col, flag)取分组内排序后,截止到当前行,取第一个值

  • 第一个参数是您想要第一个值的列
  • 第二个(可选)参数默认情况是false,不跳过空值。如果设置为 true 则跳过空值

案例

# 查询用户最后一次登录日期和pv
select 
  user_name, 
  login_date, 
  LAST_VALUE(login_date) over (
    partition by user_name 
    order by 
      login_date
  ) as last_login_date, 
  ROW_NUMBER() over (
    partition by user_name 
    order by 
      login_date
  ) AS rn, 
  LAST_VALUE(pv) over (
    partition by user_name 
    order by 
      login_date
  ) as last_pv 
from 
  default.test_user_login;

查询结果

user_namelogin_datelast_login_datepvrnlast_pv
zhangsan2021-01-012021-01-02314
zhangsan2021-01-022021-01-02424
lisi2021-01-012021-01-02516
lisi2021-01-022021-01-02626
wangwu2021-01-012021-01-01212

加window子句案例

# 查询用户最后一次登录日期和pv
select 
  user_name, 
  login_date, 
  LAST_VALUE(login_date) over (
    partition by user_name 
    order by 
      login_date
  ) as last_login_date, 
  ROW_NUMBER() over (
    partition by user_name 
    order by 
      login_date
  ) AS rn, 
  LAST_VALUE(pv) over (
    partition by user_name 
    order by 
      login_date
  ) as last_pv, 
  LAST_VALUE(pv) over (
    partition by user_name 
    order by 
      login_date rows between unbounded preceding 
      and current row
  ) as window_last_pv 
from 
  default.test_user_login;

查询结果

user_namelogin_datelast_login_datepvrnlast_pvwindow_last_pv
zhangsan2021-01-012021-01-023143
zhangsan2021-01-022021-01-024244
lisi2021-01-012021-01-025165
lisi2021-01-022021-01-026266
wangwu2021-01-012021-01-012122

4.3 聚合函数

目前支持5个带有聚合意义的窗口函数

  • COUNT
  • SUM
  • MIN
  • MAX
  • AVG

以SUM为例

select 
  user_name, 
  login_date, 
  pv, 
  ROW_NUMBER() over (
    partition by user_name 
    order by 
      login_date
  ) AS rn, 
  sum(pv) over (
    partition by user_name 
    order by 
      login_date
  ) as sum_pv_1, --默认情况,包括从开始到结束的所有行
  sum(pv) over (
    partition by user_name 
    order by 
      login_date ROWS BETWEEN UNBOUNDED PRECEDING 
      AND CURRENT ROW
  ) as sum_pv_2, --表示从起点到当前行
  sum(pv) over (partition by user_name) as sum_pv_3, --表示窗口内所有行
  sum(pv) over (
    partition by user_name 
    order by 
      login_date ROWS BETWEEN UNBOUNDED PRECEDING 
      AND UNBOUNDED sum
  ) as sum_pv_4, --表示起点到终点
  SUM(pv) over (
    partition by user_name 
    order by 
      login_date ROWS BETWEEN 2 PRECEDING 
      AND 1 FOLLOWING
  ) as sum_pv_5 --表示前2行到后面1行
from 
  default.test_user_login;

查询结果

user_namelogin_datepvrnsum_pv_1sum_pv_2sum_pv_3sum_pv_4sum_pv_5
zhangsan2021-01-013173777
zhangsan2021-01-024277777
lisi2021-01-0151115111111
lisi2021-01-02621111111111
wangwu2021-01-012122222

4.4 分析函数

为了体现函数的差异,重新造一批数据

新测试数据如下

user_namelogin_datepv
zhangsan2021-01-013
zhangsan2021-01-024
zhangsan2021-01-033
lisi2021-01-015
lisi2021-01-026
lisi2021-01-035
wangwu2021-01-012

生成排序的3个函数

  • ROW_NUMBER() 从1开始,按照顺序,生成分组内记录的序列,row_number()的值不会存在重复,当排序的值相同时,按照表中记录的顺序进行排列

  • RANK 生成数据项在分组中的排名,排名相等会在名次中留下空位

  • DENSE_RANK 生成数据项在分组中的排名,排名相等会在名次中不会留下空位

案例

select 
  user_name, 
  login_date, 
  pv, 
  ROW_NUMBER() over (
    partition by user_name 
    order by 
      pv
  ) as rank_pv_1, 
  RANK() over (
    partition by user_name 
    order by 
      pv
  ) as rank_pv_2, 
  DENSE_RANK() over (
    partition by user_name 
    order by 
      pv
  ) as rank_pv_3 
from 
  default.test_user_login;

查询结果

user_namelogin_datepvrank_pv_1rank_pv_2rank_pv_3
zhangsan2021-01-013111
zhangsan2021-01-033211
zhangsan2021-01-024332
lisi2021-01-015111
lisi2021-01-035211
lisi2021-01-026332
wangwu2021-01-012111

3个函数的使用场景:

分组取前3,比如,查询每个班级总分排名前3的学生,每个科目排名第一的学生名称

# 查询每个班级总分排名前3的学生,表结构 stu_id,class_id,subject,score
select 
  tmp1.stu_id, 
  tmp1.calss_id, 
  tmp1.sum_score 
from 
  (
    select 
      tmp.stu_id, 
      tmp.calss_id, 
      tmp.sum_score, 
      row_number() over (
        partition by class_id 
        order by 
          sum_score
      ) as rn 
    from 
      (
        select 
          stu_id, 
          calss_id, 
          sum(score) as sum_score
      ) 
    from 
      student 
    group by 
      calss_id, 
      stu_id
  ) tmp
) tmp1 
where 
  tmp1.rn >= 3;

连续登陆天数大于3天的用户

# 连续登陆天数大于3天的用户
select 
  user_name, 
  count(1) as cnt 
from 
  (
    select 
      user_name, 
      login_date, 
      row_number() over (
        partition by user_name 
        order by 
          login_date
      ) as rn
  ) 
group by 
  user_name, 
  date_sub(login_date, rn) 
having 
  count(1) > 3;
  • CUME_DIST 小于等于当前值的行数/分组内总行数

  • PERCENT_RANK (分组内当前行的RANK值-1) / (分组内总行数-1)

案例

select 
  user_name, 
  login_date, 
  pv, 
  RANK() over (partition by user_name order by pv) as rank_pv_2, 
  CUME_DIST() over (
    partition by user_name 
    order by 
      pv
  ) as cume_pv, 
  PERCENT_RANK() over (
    partition by user_name 
    order by 
      pv
  ) as percent_pv 
from 
  default.test_user_login;

查询结果

user_namelogin_datepvrank_pv_1cume_pvpercent_pv
zhangsan2021-01-01310.666666667(1-1)/(3-1)=0
zhangsan2021-01-03310.666666667(1-1)/(3-1)=0
zhangsan2021-01-02430.333333333(3-1)/(3-1)=1
lisi2021-01-01510.666666667(1-1)/(3-1)=0
lisi2021-01-03510.666666667(1-1)/(3-1)=0
lisi2021-01-02630.333333333(3-1)/(3-1)=1
wangwu2021-01-01211(1-1)/(1-1)=0
  • NTILE(n) 用于将分组数据按照顺序切分成n片,返回当前切片值,如果切片不均匀,默认增加第一个切片的分布

案例

select 
  user_name, 
  login_date, 
  pv, 
  NTILE(2) over (
    partition by user_name 
    order by 
      pv
  ) as ntile_pv_1, 
  NTILE(3) over (
    partition by uname 
    order by 
      pv
  ) as ntile_pv_2, 
  NTILE(4) over (
    partition by uname 
    order by 
      pv
  ) as ntile_pv_3 
from 
  default.test_user_login;

查询结果

user_namelogin_datepvntile_pv_1ntile_pv_2ntile_pv_3
wangwu2021-01-012111
zhangsan2021-01-013111
zhangsan2021-01-033112
zhangsan2021-01-024122
lisi2021-01-015223
lisi2021-01-035233
lisi2021-01-026234

开窗函数官方文档

六、元数据

元数据详解:
https://www.cnblogs.com/qingyunzong/p/8710356.html

元数据统计信息收集思路:
https://blog.csdn.net/songjifei/article/details/104706737

元数据详细信息收集思路:
https://blog.csdn.net/sanbudeyu_008/article/details/102800508

七、Hive优化

1 设置合理的map/reduce 数量

1.1 调整map数量

目的就是让单个map处理合适数据量的数据。

1.1.1 数据量小,减少map数

当有几百个小文件时,可以通过调整参数合并文件,减少文件数

设置如下参数:

# 设置map任务的文件切割大小为100MB
set mapred.max.split.size=100000000;
# 设置每个节点的文件切割大小为100MB
set mapred.min.split.size.per.node=100000000;
# 设置每个机架的文件切割大小为100MB
set mapred.min.split.size.per.rack=100000000;
# 设置执行map前先合并文件
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;

前面三个参数确定合并文件块的大小,大于文件块大小128m的,按照128m来分隔,
小于128m,大于100m的,按照100m来分隔,把那些小于100m的(包括小文件和分隔大文件剩下的)进行合并。

1.1.2 数据量大,增加map数

当文件很大,只用1个map去处理很费时间,此时可以增加map数量(按自己集群情况调整)。

设置如下参数:

set mapred.map.tasks=10;

1.2 调整reduce数量

如果设置了mapred.reduce.tasks/mapreduce.job.reduces参数,那么Hive会直接使用它的值作为Reduce的个数;如果mapred.reduce.tasks/mapreduce.job.reduces的值没有设置(也就是-1),那么Hive会根据输入文件的大小估算出Reduce的个数。根据输入文件估算Reduce的个数可能未必很准确,因为Reduce的输入是Map的输出,而Map的输出可能会比输入要小,所以最准确的数根据Map的输出估算Reduce的个数。

reduce个数的设定极大影响任务执行效率,不指定reduce个数的情况下,Hive会猜测确定一个reduce个数,基于以下两个设定:

hive.exec.reducers.bytes.per.reducer(每个reduce任务处理的数据量,默认为1000^3=1G)
hive.exec.reducers.max(每个任务最大的reduce数,默认为999)

计算reducer数的公式 N=min(参数2,总输入数据量/参数1)
如果reduce的输入(map的输出)总大小不超过1G,那么只会有一个reduce任务;

1.2.1 方法1

调整如下参数

# 设置每个reduce处理的文件大小设置为500MB
set hive.exec.reducers.bytes.per.reducer=500000000;
1.2.2 方法2

调整如下参数

# 设置reduce的数量为15
set mapred.reduce.tasks=15;

2 小文件优化

小文件产生可能的原因:

1 动态分区插入数据,产生大量的小文件,从而导致map数量剧增;
2 reduce数量越多,小文件也越多(reduce的个数和输出文件是对应的);
3 数据源本身就包含大量的小文件

小文件的影响:

  1. 从Hive的角度看,小文件会开很多map,一个map开一个JVM去执行,所以这些任务的初始化,启动,执行会浪费大量的资源,严重影响性能。
  2. 在HDFS中,每个小文件对象约占150byte,如果小文件过多会占用大量内存。这样NameNode内存容量严重制约了集群的扩展。

源头解决思路:

1 减少reduce的数量(可以使用参数进行控制);
2 少用动态分区,用时记得按distribute by分区;

已有小文件解决思路:

1 使用hadoop archive命令把小文件进行归档;
2 重建表,建表时减少reduce数量;
3 通过参数进行调节,设置map/reduce端的相关参数

可调整的map/reduce参数:

# 每个Map最大输入大小(这个值决定了合并后文件的数量)  
set mapred.max.split.size=100000000;
# 一个节点上split的至少的大小(这个值决定了多个DataNode上的文件是否需要合并)
set mapred.min.split.size.per.node=100000000; 
# 一个交换机下split的至少的大小(这个值决定了多个交换机上的文件是否需要合并)    
set mapred.min.split.size.per.rack=100000000;  
# 执行Map前进行小文件合并  
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat; 

# 设置map端输出进行合并,默认为true  
set hive.merge.mapfiles = true  
# 设置reduce端输出进行合并,默认为false  
set hive.merge.mapredfiles = true  
# 设置合并文件的大小  
set hive.merge.size.per.task = 256*1000*1000  
# 当输出文件的平均大小小于该值时,启动一个独立的MapReduce任务进行文件merge。  
set hive.merge.smallfiles.avgsize=16000000 

3 SQL优化

优化思路:

  1. 尽早尽量过滤数据,减少每个阶段的数据量
  2. 减少job数
  3. 减少扫描的文件数或分区数

案例表

# t_order 订单表
create table t_order(
	order_id int "订单编号",
	user_id int "用户编号",
	create_date string "订单创建时间"
);
# t_user 用户表
create table t_user(
	user_id int "用户编号",
	user_name string "用户名",
	create_date string "创建时间"
);

3.1 列剪裁

一个表中有多个字段,查询时只查询需要的列

# 优化前
select * from t_user;
# 优化后
select user_id from t_user;

3.2 分区剪裁

数据量大的表必须是分区表,查询时where里必须指定分区的筛选条件

# 优化前
select * from tablename;
# 优化后
select order_id,order_name from t_order where create_date>='2020-01-01';

3.3 避免笛卡尔积

关联查询时必须加关联条件

# 优化前
select order_id,user_name from t_order, t_user;

select order_id,user_name from t_order inner join t_user;
# 优化后
select order_id,user_name from t_order, t_user where t_order.user_id = t_user.user_id;

select order_id,user_name from t_order inner join t_user on t_order.user_id=t_user.user_id;

3.4 join前过滤无用数据

# 优化前
select o.order_id,u.user_name  
from t_order o 
join t_user u on (o,user_id=u.user_id)  
where o.create_date = '2020-01-01' and u.create_date='2020-01-01';
# 优化后
select o2.order_id,u2.user_name from (select o.order_id,o.user_id from t_order o where o.create_date = '2020-01-01') o2 join (select user_id,user_name from t_user u where u.create_date='2020-01-01') u2 on o2.user_id = u2.user_id;

3.5 join时小表在前大表在右

在Reduce阶段,位于join操作符左边的表会先被加载到内存,载入条目较少的表可以有效的防止内存溢出(OOM)。

# 优化前
select o.order_id,o.user_name from t_order o join t_user u on o.user_id=u.user_id;
# 优化后
select o.order_id,o.user_name from t_user u join  t_order o on u.user_id=o.user_id;

3.6 mapjoin

join有两种,一种是map join,一种是reduce join。当小表和大表进行join时,尽量采用mapjoin;如果把join的操作先在map join,到reduce后接收到的数据会更少,同时可以避免小表和大表join产生的数据倾斜;

注意:一般行数小于2000行,大小小于1M(扩容后可以适当放大)的表才能使用

有2种使用方式
1 自动mapjoin默认是已开启的,只需要小表放左边即可
2 显示指定mapjoin

# 方式1

set hive.auto.convert.join = true;# 开启自动mapjoin
set hive.mapjoin.smalltable.filesize = 6250000;# 设置小表存储的文件大小
 
select * from t_user
join t_order 
on t_user.user_id=t_order.user_id;

# 方式2

set hive.ignore.mapjoin.hint=true;
 
select /*+ mapjoin(t_user) */   * 
from t_user
join t_order 
on t_user.user_id=t_order.user_id;

如果是mapjoin会有如下日志
在这里插入图片描述

3.7 避免distinct

distinct使用一个reduce效率很慢,建议使用group by替代

# 优化前
select distinct user_id from t_order;
# 优化后
select user_id  from t_order group by user_id;

3.8 explain查看hive查询的执行计划

可以根据执行计划的情况,调整sql

语法:

EXPLAIN [EXTENDED|CBO|AST|DEPENDENCY|AUTHORIZATION|LOCKS|VECTORIZATION|ANALYZE] query

关键字:

  • EXTENDED 查看详细信息
  • CBO 查看Calcite优化器生成的执行计划,还可以查看每个操作花费的资源(cpu,io)
  • AST 查看抽象语法树
  • DEPENDENCY 查看本次输入的额外信息,包括表或其他的一些属性
  • AUTHORIZATION 查看查询权限
  • LOCKS 查看本次查询所需要的锁
  • VECTORIZATION 查看本次查询不是向量化执行的原因
  • ANALYZE 查看计划和实际行数

案例:

EXPLAIN
FROM src INSERT OVERWRITE TABLE dest_g1 SELECT src.key, sum(substr(src.value,4)) GROUP BY src.key;

依赖图:

STAGE DEPENDENCIES:
  Stage-1 is a root stage
  Stage-2 depends on stages: Stage-1
  Stage-0 depends on stages: Stage-2

Stage-1是根步骤,Stage-2在Stage-1执行完之后执行,Stage-0在Stage-2执行完后执行。

STAGE PLANS:
  Stage: Stage-1
    Map Reduce
      Alias -> Map Operator Tree:
        src
            Reduce Output Operator
              key expressions:
                    expr: key
                    type: string
              sort order: +
              Map-reduce partition columns:
                    expr: rand()
                    type: double
              tag: -1
              value expressions:
                    expr: substr(value, 4)
                    type: string
      Reduce Operator Tree:
        Group By Operator
          aggregations:
                expr: sum(UDFToDouble(VALUE.0))
          keys:
                expr: KEY.0
                type: string
          mode: partial1
          File Output Operator
            compressed: false
            table:
                input format: org.apache.hadoop.mapred.SequenceFileInputFormat
                output format: org.apache.hadoop.mapred.SequenceFileOutputFormat
                name: binary_table
 
  Stage: Stage-2
    Map Reduce
      Alias -> Map Operator Tree:
        /tmp/hive-zshao/67494501/106593589.10001
          Reduce Output Operator
            key expressions:
                  expr: 0
                  type: string
            sort order: +
            Map-reduce partition columns:
                  expr: 0
                  type: string
            tag: -1
            value expressions:
                  expr: 1
                  type: double
      Reduce Operator Tree:
        Group By Operator
          aggregations:
                expr: sum(VALUE.0)
          keys:
                expr: KEY.0
                type: string
          mode: final
          Select Operator
            expressions:
                  expr: 0
                  type: string
                  expr: 1
                  type: double
            Select Operator
              expressions:
                    expr: UDFToInteger(0)
                    type: int
                    expr: 1
                    type: double
              File Output Operator
                compressed: false
                table:
                    input format: org.apache.hadoop.mapred.TextInputFormat
                    output format: org.apache.hadoop.hive.ql.io.IgnoreKeyTextOutputFormat
                    serde: org.apache.hadoop.hive.serde2.dynamic_type.DynamicSerDe
                    name: dest_g1
 
  Stage: Stage-0
    Move Operator
      tables:
            replace: true
            table:
                input format: org.apache.hadoop.mapred.TextInputFormat
                output format: org.apache.hadoop.hive.ql.io.IgnoreKeyTextOutputFormat
                serde: org.apache.hadoop.hive.serde2.dynamic_type.DynamicSerDe
                name: dest_g1

这个例子里有2个map/reduce步骤和1个文件系统相关的步骤。Stage-1是读取src表的数据并做一些转换;Stage-2做分组聚合;Stage-0最后将数据移动到表相关的目录里。

explain 官方文档:
https://cwiki.apache.org/confluence/display/Hive/LanguageManual+Explain#LanguageManualExplain-EXPLAINSyntax

参考资料

Hive官方网站

《hive编程指南》

Hive SQL 底层执行过程详细剖析

开窗和分析函数:
hive窗口函数总结
hive窗口分析总结

Hive优化(整理版)

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值