1.
Phoenix的一级索引就是它的主键,对应的就是hbase的rowkey,这个是默认的机制,我们不需要额外操作。
故二级索引就是非主键/rowkey列的索引。创建二级索引的目的就是为了加快查询速度。
Hbase只能基于rowkey去查询数据,要是基于其他列查询数据就只能使用filter的功能,但是效果十分不好。他是全表扫描,性能比较差。通过Phoenix创建索引可以避免全表扫描,因此就增加了查询的效率。
2.二级索引配置
<!-- phoenix regionserver 配置参数--> <property> <name>hbase.regionserver.wal.codec</name> <value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value> </property>
创建测试表和测试数据:
create table t_index ( id varchar(20) primary key, name varchar(20) , addr varchar(20) ) COLUMN_ENCODED_BYTES = NONE; upsert into t_index(id , name , addr) values('1001' , 'zhangsan' ,'beijing'); upsert into t_index(id , name , addr) values('1002' , 'lisi' ,'shanghai');
3.全局二级索引
所谓的全局二级索引,就是将索引列与原表的rowkey组合起来,当成索引表的rowkey来使用。
没有创建索引之前,使用非主键查询,用explain分析是FULL SCAN,说明全部扫描
0: jdbc:phoenix:> explain select id , name from t_index where name = 'zhangsan' ; +-------------------------------------------------------------------+-----------------+----------------+--------------+ | PLAN | EST_BYTES_READ | EST_ROWS_READ | EST_INFO_TS | +-------------------------------------------------------------------+-----------------+----------------+--------------+ | CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN FULL SCAN OVER T_INDEX | null | null | null | | SERVER FILTER BY NAME = 'zhangsan' | null | null | null | +-------------------------------------------------------------------+-----------------+----------------+--------------+ 2 rows selected (0.116 seconds)
给name列创建索引:
0: jdbc:phoenix:> create index idx_t_index_name on t_index(name) ;
可以看到在name列创建索引后,查询就变成了RANGE SCAN扫描,只需要扫描某个范围,而不需要全表扫描。
0: jdbc:phoenix:> explain select id , name from t_index where name = 'zhangsan' ; +------------------------------------------------------------------------------------------+-----------------+----------------+--------------+ | PLAN | EST_BYTES_READ | EST_ROWS_READ | EST_INFO_TS | +------------------------------------------------------------------------------------------+-----------------+----------------+--------------+ | CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN RANGE SCAN OVER IDX_T_INDEX_NAME ['zhangsan'] | null | null | null | | SERVER FILTER BY FIRST KEY ONLY | null | null | null | +------------------------------------------------------------------------------------------+-----------------+----------------+--------------+
4.全局二级索引实现原理
phoenix索引本质上就是一张表,只不过表的类型是索引类型,表中信息如下所示:
0: jdbc:phoenix:> select * from T_INDEX; +-------+-----------+-----------+ | ID | NAME | ADDR | +-------+-----------+-----------+ | 1001 | zhangsan | beijing | | 1002 | lisi | shanghai | +-------+-----------+-----------+
通过Hbase查询索引表:可以看到Hbase 把name列和原本的rowkey拼接起来,作为一个rowkey当成索引表里面的rowkey来进行使用。
hbase(main):006:0> scan 'IDX_T_INDEX_NAME'' ROW COLUMN+CELL lisi\x001002 column=0:_0, timestamp=1672136557063, value=x zhangsan\x001001 column=0:_0, timestamp=1672136557063, value=x
这个时候查询name就是基于IDX_T_INDEX_NAME 索引表来过滤了,因为name在rowkey里面就可以实现快速过滤。
特别说明,要是查询的时候多了一个非索引字段,就会变成FULL SCAN的全表扫描,因为非索引字段不在rowkey中,索引中没有它所以无法走索引,为了保证数据可以查询出来,就需要通过源表进行查询。
0: jdbc:phoenix:> explain select id , name ,addr from t_index where name = 'zhangsan' ; +-------------------------------------------------------------------+-----------------+----------------+--------------+ | PLAN | EST_BYTES_READ | EST_ROWS_READ | EST_INFO_TS | +-------------------------------------------------------------------+-----------------+----------------+--------------+ | CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN FULL SCAN OVER T_INDEX | null | null | null | | SERVER FILTER BY NAME = 'zhangsan' | null | null | null | +-------------------------------------------------------------------+-----------------+----------------+--------------+
5.复合索引
上面问题不能走索引表的原因是因为索引表中没有要查询的列,那么我们最简单的想法就是给需要查询的列创建索引就好了。复合索引就是同时给name和addr建索引,因为单独创建索引的话,索引会创建到两个位置,没法一起查询。
使用示例:
drop index idx_t_index_name on t_index ;
2、创建复合索引
create index idx_t_index_name_addr on t_index(name , addr) ;
查询验证:从全表扫描变成了范围扫描
0: jdbc:phoenix:> explain select id , name ,addr from t_index where name = 'zhangsan' ; +-----------------------------------------------------------------------------------------------+-----------------+----------------+--------------+ | PLAN | EST_BYTES_READ | EST_ROWS_READ | EST_INFO_TS | +-----------------------------------------------------------------------------------------------+-----------------+----------------+--------------+ | CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN RANGE SCAN OVER IDX_T_INDEX_NAME_ADDR ['zhangsan'] | null | null | null | | SERVER FILTER BY FIRST KEY ONLY | null | null | null | +-----------------------------------------------------------------------------------------------+-----------------+----------------+--------------+
特别说明:使用复合索引一定要注意查询的顺序,索引顺序不对不走索引,因为索引表是对位比较,可以单独使用左边的索引,但是不能先使用右边的索引
explain select id , name ,addr from t_index where addr = 'beijing' ; // FULL SCAN OVER IDX_T_INDEX_NAME_ADDR explain select id , name ,addr from t_index where name = 'zhangsan' and addr = 'beijing' ; //RANGE SCAN explain select id , name ,addr from t_index where addr = 'beijing' and name = 'zhangsan' ; //RANGE SCAN
复合索引虽然可以解决上面的问题,但是不太妥当,因为有点儿浪费,咱们要给某个列建索引一定是要用到where过滤里面的,只有过滤数据的时候,索引才会发挥威力。要是我们只是基于name做过滤,只是想在查询的时候查到addr就有些得不偿失了,因为索引的代价是很高的。
6.包含索引
包含索引就是创建携带其他字段的全局索引
就是相当于创建索引的时候将addr带入到索引中,但是不给addr创建索引,这样就既可以查询出addr的数据,又没有创建索引的代价。
1、删除之前的索引
drop index idx_t_index_name_addr on t_index ;
2、创建包含索引
create index idx_t_index_name on t_index(name) include(addr) ;
查询验证:
0: jdbc:phoenix:> explain select id , name ,addr from t_index where name = 'zhangsan' ; +------------------------------------------------------------------------------------------+-----------------+----------------+--------------+ | PLAN | EST_BYTES_READ | EST_ROWS_READ | EST_INFO_TS | +------------------------------------------------------------------------------------------+-----------------+----------------+--------------+ | CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN RANGE SCAN OVER IDX_T_INDEX_NAME ['zhangsan'] | null | null | null | +------------------------------------------------------------------------------------------+-----------------+----------------+--------------+
查询Hbase中索引的构建:可以看到索引列是基于rowkey进行存储的,但是addr字段是当成Hbase一个普通的列,带入到索引表中
hbase(main):009:0> scan 'IDX_T_INDEX_NAME' ROW COLUMN+CELL lisi\x001002 column=0:0:ADDR, timestamp=1672141040572, value=shanghai lisi\x001002 column=0:_0, timestamp=1672141040572, value=x zhangsan\x001001 column=0:0:ADDR, timestamp=1672141040572, value=beijing zhangsan\x001001 column=0:_0, timestamp=1672141040572, value=x
7.本地索引
本地索引就是将索引列与表的rowkey组合起来,当成表的rowkey来使用,索引和数据都存放在一张表中。
使用示例:
drop index idx_t_index_name on t_index ;
2、创建本地索引
create local index idx_t_index_name on t_index(name);
查询验证:
0: jdbc:phoenix:> explain select id , name ,addr from t_index where name = 'zhangsan' ; +-----------------------------------------------------------------------------------+-----------------+----------------+--------------+ | PLAN | EST_BYTES_READ | EST_ROWS_READ | EST_INFO_TS | +-----------------------------------------------------------------------------------+-----------------+----------------+--------------+ | CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN RANGE SCAN OVER T_INDEX [1,'zhangsan'] | null | null | null | | SERVER FILTER BY FIRST KEY ONLY | null | null | null | +-----------------------------------------------------------------------------------+-----------------+----------------+--------------+
通过源表查询Hbase中索引的构建:
hbase(main):011:0> scan 'T_INDEX'' ROW COLUMN+CELL \x00\x00lisi\x001002 column=L#0:_0, timestamp=1672135975572, value=_0 \x00\x00zhangsan\x001001 column=L#0:_0, timestamp=1672135974823, value=_0 1001 column=0:ADDR, timestamp=1672135974823, value=beijing 1001 column=0:NAME, timestamp=1672135974823, value=zhangsan 1001 column=0:_0, timestamp=1672135974823, value=x 1002 column=0:ADDR, timestamp=1672135975572, value=shanghai 1002 column=0:NAME, timestamp=1672135975572, value=lisi 1002 column=0:_0, timestamp=1672135975572, value=x
8.怎么选择索引
全局索引:适合多读少写的业务场景,因为写数据的时候会消耗大量开销,因为索引表也要更新,而索引表是分布在不同的数据节点上的,跨节点的数据传输带来了较大的性能消耗。在读数据的时候Phoenix会选择索引表来降低查询消耗的时间。
本地索引:适用于写操作频繁的场景。索引数据和数据表的数据是存放在同一张表中(且是同一个Region),避免了在写操作的时候往不同服务器的索引表中写索引带来的额外开销。
转载请注明:西门飞冰的博客 » Hbase整合Phoenix之二级索引