Hive索引

Hive索引

文章目录

Compact索引

CREATE INDEX idx_xxx ON TABLE xxx(tablename) AS 'org.apache.hadoop.hive.ql.index.compact.CompactIndexHandler' WITH DEFERRED REBUILD;

使用 show tables; 命令查看,发现现在多了一个叫 default__xxx_idx_xxx__ 的表,但是里面没有内容,因为CREATE INDEX 命令后面加了 WITH DEFERRED REBUILD 选项,所以索引不会自动创建,而是要执行:ALTER INDEX idx_xxx ON xxx REBUILD; 才会重建索引。

如果有分区的话,可以针对单独的分区重建索引: ALTER INDEX idx_xxx ON xxx PARTITION(month='2019-09') REBUILD;

HiveHive索引有很大的局限,其中之一就是占用空间。根据之前有位大佬(不是我)的测试:xxx表其中一个108G的分区,索引需要32G的左右空间(大佬说是在没有压缩的情况下,他还未验证索引是否可以压缩)。

REBUILD 索引后,会发现索引的分区结构和原表一致,对于Compact索引,其实就是索引字段名,文件,偏移值这三个字段组成的表。

可以用 select * from default__xxx_idx_xxx__; 查看。或用describe idx_xxx 会发现他有3个字段:

name  			string
_bucketname  	string
_offsets		array<bigint>

其中:

name :是原xxx表种的字段
_bucketname :是xxx表在HDFS中的文件名。
_offsets :是该name在_bucketname 文件中的便宜地址,是一个数组。

例如:name=‘zhangsan’ 的字段在xxx表中出现了3次,那么 _offsets 就可能会有3个值(之所以为可能,因为存在3个文件中个出现一次的情况),分别是 3次 ‘zhangsan’ 在文件中的偏移量。

索引第二个缺点就是使用方法并不统一。例如我对name建立索引后,需要查询 select count(*) from xxx where name = 'zhangsan' 时,要写成:

select size(`_offsets`) from default__xxx_idx_xxx__ where name = 'zhangsan';

注意 _offsets 前后有反引号 `。

因为正常情况下,Hive字段名不允许以 _ 开头, _bucketname 和 _offsets 是隐藏字段,为了能够正常使用它,压价反引号,但不能用普通单引号(否则就直接变成字符串了)。

如果想要优化 select count(*) from xxx where name = 'zhangsan' 则要用下面复杂的步骤:

INSERT OVERWRITE DIRECTORY '/tmp/result' 
select  '_bucketname','_offsets' from default__xxx_idx_xxx__ where name = 'zhangsan';
set hive.index.compact.file=/tmp/result;
set hive.input.format=org.apache.hadoop.hive.ql.index.compact.HiveCompactIndexInputFormat;
select * from xxx where name = 'zhangsan';

而且索引效果不明显,甚至有时候会更差。

但类似 select name,count(*) from xxx group by name; 这种查询时,因为实际只需要扫描一次 default__xxx_idx_xxx__ 并取每个 size 就可以了,而 default__xxx_idx_xxx__ 要比原表 xxx 小得多。再大佬得分享中,直接对xxx表进行 group by 耗时 260秒,而从索引查询只耗时 100秒。

可见索引虽然使用场景有限,并且效果不稳定,但是的确能够减少全表扫描的范围,减少IO量。在大佬分享中,不使用索引时,需要扫描434个MR task。而在使用索引后,第一阶段只需要扫描30G的文件,第二阶段直接定位到 _bucketname 的文件中(分享中 name='zhangsan’的值只有一个),所以总共只占用了122个MR task。

Aggregation索引

CREATE INDEX idx2_xxx ON TABLE tablename AS 'org.apache.hadoop.hive.ql.index.AggregateIndexHandler'
WITH DEFERRED REBUILD IDXPROPERTIES("AGGREGATES"="count(value)") STORED AS TEXTFILE;

其中 AGGREGATES 表示需要聚合的操作,本例中为 count,也可以改为 sum。同样用 ALTER 进行 REBUILD。

DESCRIBE default__xxx_idx2_xxx__可以看到其结构:

name  			string
_bucketname  	string
_offsets		array<bigint>
_count_of_value	bight

也就是说

select count(value) from xxx where name='zhangsan';

现在可以改为:

select `_count_of_value` from xxx where name='zhangsan';

另一方面可以看到:CompactIndex 所包含的字段已经在 AggregateIndex 里面了,即 AggregateIndex 可以当作 CompactIndex 用了。

如果将 IDXPROPERTIES 里面的 count 换成 sum,那么最后一个字段将是 _sum_of_value ,但奇怪的是,类型仍然为bigint。而不是double。但是这个问题可以通过修改字段类型将其修改为 double。

ALTER TABLE default__xxx_idx2_xxx__ CHANGE `_sum_of_value` `_sum_of_value` double;

在大佬的分享中,108G的分区建立的聚合索引大小为48.8G,因为 Aggregation 实际上包含了 Compact,所以更大了。

如果表中name 字段的重复值很多,这个空间大小应该可以降下来,上面案例中的重复值为 1/3.

Bitmap索引

Hive中的位图索引非常不完善,几乎不可用。

大佬分享的案例:

有表,数据如下:

name country gender
zhangsan China male
lily China male
Tom USA male
xiaofang China famale

可以为country字段建立位图索引如下:

china 1101
USA 0010

再为gender建立性别的位图索引,如下:

male 1110
famale 0001

现在要查询数据库中来自中国的男性:

select * from xxx where country='China' and gender='male';

可以直接从country索引中取出 China的位图 1101 ,再从gender 索引中取出 male 的位图 1110,然后进行 & 操作,得到 1100,于是可以直接选取第 1,2行数据。

在Hive中,用上面说的生成位图索引如下:

索引字段名 _bucketname _offset _bitmaps
y hdfs://hacluster/user/hive/warehouse/xxx/xxx.txt 0 [1,2,4,8589934592,1,0]
n hdfs://hacluster/user/hive/warehouse/xxx/xxx.txt 6 [1,2,4,8589934592,1,0]

先看看每个字段的含义,用 describe 命令可以看到位图索引由4个字段,分别是索引字段名,文件名 _bucketname,偏移量 _offset,位图 _bitmaps

其中最后那串古怪的数字就是字段 _bitmaps ,类型是 bitint 数组: [1,2,4,8589934592,1,0]

这是EWAHCompressedBitmap 的格式,EWAHCompressedBitmap 是一种压缩的 Bitmap Set,尝试了一下,发现第一位表示 size in bits,表示最大位 bit 位,第二位表示 size in words,表示后面用了几个long来保存数据。整张有意义的是 [8589934592,1] 这两个数字。

其中 8589934592 转换位二进制是 1 后面 33 个 0,猜测 8589934592 低 33 位表示是否压缩,这里全为 0,表示不压缩(所谓压缩,其实就是将全 0 的跳过);位 33 值为 1,表示后面 1个 long 值是有效值,而后面的long值就是 1,所以这么长的遗传数组,其实想表达的就是1而已。

从上面结果来看,根本不是位图索引。首先位图索引应该是每个取值只有1行,不会有重复的 y 和 n。其次,位图索引应该是一个大整数(更多情况是long 的数组,数组大小就是整个表大小除以 64),且y 和 n的 _bitmaps 应各不相同。

所以正确的位图索引应该是:

索引字段名 _bucketname _offset _bitmaps
y hdfs://hacluster/user/hive/warehouse/xxx/xxx.txt [0,12,24,36,48,60] [1365]
n hdfs://hacluster/user/hive/warehouse/xxx/xxx.txt [6,18,30,42,54,66] [2730]

其中 [0,12,24,36,48,60] 是 hdfs://hacluster/user/hive/warehouse/xxx/xxx.txt 中所有字段值为 y 的偏移地址,而1365 的二进制就是 010101010101 (注意这里是反序的,这里假设最低位表示0行,越往左行号越大)。否则便利原表的 y 值,在索引表中还有重新遍历一次,根本没有发挥位图索引的作用。

108G的分区,Hive建立的位图索引大小为 54.2G,光是那个 Bitmap 数组就花了不少。

所以结论:Hive索引很不完善,效果不加。

主要缺点:占用空间大,精确查询反而降低,使用麻烦,不便于二次开发。

主要优点:对于count 和 sum 提升明显。

上一篇:完美主义者的最终框架


下一篇:php单引号和双引号的区别