- Hive是反模式设计,按天划分表也是一种模式,通常会在表名中加入一个时间戳,例如test_2010_01_01,这种每天一张表的模式在数据库领域是反模式的一种方式。
# 传统反模式
create table test_2020_05_01(id int);
create table test_2020_05_02(id int);
create table test_2020_05_03(id int);
# 查询1、2号数据
select * from test_2020_05_01
union all
select * from test_2020_05_02
where id >4;
# hive分区表
create table test(id int) partitioned by(ds string);
alter table test add partition(ds=20200501);
alter table test add partition(ds=20200502);
select * from where ds='20200501' and ds='20200502';
- HDFS用于设计存储数百万的大文件,而非小文件, 使用过多分区可能导致的一个问题就是会创建大量的非必须的Hadoop文件和文件夹。一个分区就对应着一个包含多个文件的文件见。如果文件过多那么namenode的内存压力就会线性增长,一个文件大约占
150
字节的nm元数据大小,但是hdfs存在一个文件总数限制,如果文件过多会超过限制 。 - MR会将一个任务(job)转换成多个任务(task),默认情况下,每个task对应一个新的jvm实例,存在创建和销毁的开销,对于小文件,每个文件对应一个新的jvm实例 。
- 按照时间划分分区是个好策略,但是需要考虑分区数量的增长的均匀的,并且每个分区下包含的文件大小至少是文件系统中块的大小或块大小的数倍。
- 另一个方案是按照两个级别的分区并且使用不同维度,如第一分区为天,第二个分区为city
-
Hive没有主键或基于序列密钥生成的自增键的概念 。如果可以的话, 应避免对非标准化数据进行连接(JOIN)操作 。复杂的数据类型,如array、map和struct,有助于实现在单行中存储一对多数据。这并不是说不应该进行标准化, 但是星形架构类型设计并非最优的 。
-
避免标准化的原因是为了最小化磁盘寻道,比如那些通常需要外键关系的情况下,非标准数据允许被扫描或写入到大的、连续的磁盘存储区域,从而优化磁盘驱动器的io性能 。但是非标准化数据会导致数据重复,而且有更大的数据不一致风险。
- Hive提供一个独特的语法,它可有从一个数据源产生多个数据聚合,而无需每次聚合都重新扫描一遍文件系统。
# 需要扫描俩次文件系统
insert overwrite table sales
select * from history where action='purchased';
insert overwrite table credits
select * from history where action='returned';
# 扫描一次
from history
insert overwrite table sales
select * where action='purchased'
insert overwrite table credits
select * where action='returned';
- 对于ETL的过程中会涉及多个处理步骤,而每个步骤可能会产生多个临时表,临时表作为分区表可能也是需要的。
$ hive -hiveconf dt=2011-01-01
# 临时表
hive> INSERT OVERWRITE table distinct_ip_in_logs
> SELECT distinct(ip) as ip from weblogs
> WHERE hit_date='${hiveconf:dt}';
hive> CREATE TABLE state_city_for_day (state string,city string);
hive> INSERT OVERWRITE state_city_for_day
> SELECT distinct(state,city) FROM distinct_ip_in_logs
> JOIN geodata ON (distinct_ip_in_logs.ip=geodata.ip);
- 如果当计算某一天的数据时会导致前一天的数据被INSERT OVERWRITE语句覆盖掉。如果同时运行两个实例,用于处理不同日期的数据的话,它们可能会相互影响到对方的结果数据。
$ hive -hiveconf dt=2011-01-01
hive> INSERT OVERWRITE table distinct_ip_in_logs
> PARTITION (hit_date=${dt})
> SELECT distinct(ip) as ip from weblogs
> WHERE hit_date='${hiveconf:dt}';
hive> CREATE TABLE state_city_for_day (state string,city string)
> PARTITIONED BY (hit_date string);
hive> INSERT OVERWRITE table state_city_for_day PARTITION(${hiveconf:df})
> SELECT distinct(state,city) FROM distinct_ip_in_logs
> JOIN geodata ON (distinct_ip_in_logs.ip=geodata.ip)
> WHERE (hit_date='${hiveconf:dt}');
- 缺点是需要管理中间表并删除旧分区。
- 分区提供一个隔离数据和优化查询的便利方式,但是不是所有数据集都适合合理的分区, 因此分桶将数据集分解成更容易的若干部分的另一个方法 。
- 假设有个表的一级分区是dt,代表日期,二级分区是user_id那么这种划分方式会导致太多的小分区。 如果用户使用动态分区来创建,默认情况下,hive会限制动态分区可能创建的最大分区数,用来避免由于创建太多分区导致至少超过了文件系统的处理能力 。
hive> CREATE TABLE weblog (url STRING, source_ip STRING)
> PARTITIONED BY (dt STRING, user_id INT);
hive> FROM raw_weblog
> INSERT OVERWRITE TABLE page_view PARTITION(dt='2012-06-08', user_id)
> SELECT server_name, url, source_ip, dt, user_id;
- 对表weblog进行分桶,并使用user_id为分桶字段,则字段会根据用户指定的值进行hash分发到桶中,同一个user_id下的记录通常会存储到一个桶内。
hive> CREATE TABLE weblog (user_id INT, url STRING, source_ip STRING)
> PARTITIONED BY (dt STRING)
> CLUSTERED BY (user_id) INTO 96 BUCKETS;
# 插入数据
hive> SET hive.enforce.bucketing = true;
hive> FROM raw_logs
> INSERT OVERWRITE TABLE weblog
> PARTITION (dt='2009-02-25')
> SELECT user_id, url, source_ip WHERE dt='2009-02-25';
- 如果没有 hive.enforce.bucketing属性,那么需要自己设置和分桶个数相匹配的reducer个数,例如设置set mapred.reduce.tasks=96
-
Hive运行在原始数据文件上定义一个模式,当数据增加新字段时,可以容易的适应表定义的模式。
-
Hive提供了SerDe抽象, 其用于从输入中提取数据,SerDe同样用于输出数据。 一个SerDe通常从左到右解析,通过指定的分隔符将行分解成列,SerDe通常是非常宽松的,如果某行的字段个数比预期更少那么缺少的字段将返回Null。如果更多,多出的字段将被忽略掉。
-
通过
Alter Table tablename add columns(test string)
增加字段。
- hive通常使用行式存储,不过Hive也提供列一个列式SerDe来格式存储信息。
- 以下数据集适合列式存储
- 有足够多的行, 一些列字段有很多重复数据,此时适合列存储 。
- 存在非常多的列,查询只会使用到一个字段或者很少的一组字段,基于列式存储将会使分析表数据执行更快。
- RCFile列式存储结构
- 几乎在所有情况下,压缩都可以使磁盘上存储的数据量变小,这样可以通过降低IO来提供查询执行速度,Hive可以无缝使用很多压缩类型,不使用压缩唯一令人信服的理由就是产生的数据用于外部系统,或者非压缩格式是最兼容的。
- 使用压缩会消耗CPU资源,MR任务往往是IO密集型的,因此CPU开销通常不是问题。