##1.测试表说明
原hbase表是只有1个列族,算上rowkey一共6个字段的hbase表。
一共37个regions,数据量一共3亿6千4百万
hbase中表描述
数据样例
数据量
##2.建立索引
hbase的二级索引在phoenix中建立。
建索引的语句如下,建好索引之后,有数据的变更索引数据和原始数据会实时的同步更新
create index car_index_index1 on car(hphm)
1
因为这张表是已经存在的大表,而且数据量也不少,直接用上面的语句建立索引时间会很长,所以这里用批量建立索引的方式
#首先在phoenix中建立索引表信息
create index car_index_datehphm on "car"("f1"."date","f1"."hphm") include ("f1"."coorid","f1"."cx","f1"."ys") async;
#这里的建立索引语句加了async,异步建立索引。另外f1是hbase中原始的列族名,这张表是原始hbase表转过来的,为什么这么写就不解释了,"f1"."date"就代表一个字段。include是什么后面再解释
#下面启动批量建立索引的mr任务
${HBASE_HOME}/bin/hbase org.apache.phoenix.mapreduce.index.IndexTool \
--data-table "car" --index-table CAR_INDEX_DATEHPHM \
--output-path ASYNC_IDX_HFILES
1
2
3
4
5
6
7
8
3亿6千万的数据,批量建立索引时间为1小时10分钟
##3.索引测试
上面的建立的索引是两个字段的复合索引。可以分别对两个字段进行单独或者一起的条件过滤查询
##车牌查询
0: jdbc:phoenix:localhost:2181:/hbase> select * from "car" where "f1"."hphm" like '苏E3G%' limit 10;
+-------------------------+---------+------+-----------------+----------+-----+
| ID | coorid | cx | date | hphm | ys |
+-------------------------+---------+------+-----------------+----------+-----+
| 20170101005027_苏E3G2SA | t | 奔驰 | 20170101005027 | 苏E3G2SA | 黄 |
| 20170101011949_苏E3GS62 | � | 奥迪 | 20170101011949 | 苏E3GS62 | 银 |
| 20170101014325_苏E3G7S8 | � | 丰田 | 20170101014325 | 苏E3G7S8 | 银 |
| 20170101022906_苏E3GPQB | D | 保时捷 | 20170101022906 | 苏E3GPQB | 黄 |
| 20170101030015_苏E3G2SA | | 奔驰 | 20170101030015 | 苏E3G2SA | 黄 |
| 20170101030157_苏E3G2SA | � | 奔驰 | 20170101030157 | 苏E3G2SA | 黄 |
| 20170101033901_苏E3G117 | � | 奥迪 | 20170101033901 | 苏E3G117 | 蓝 |
| 20170101040047_苏E3G7S8 | | 丰田 | 20170101040047 | 苏E3G7S8 | 银 |
| 20170101040114_苏E3G8CW | * | 宝马 | 20170101040114 | 苏E3G8CW | 黄 |
| 20170101041608_苏E3G6GH | $ | 奔驰 | 20170101041608 | 苏E3G6GH | 红 |
+-------------------------+---------+------+-----------------+----------+-----+
10 rows selected (1.767 seconds)
用时1.767秒,分析原因为车牌种类比较少,筛选出的速度会稍微慢点,如果建立二级索引第一个字段用车牌,速度应该会快很多。
## 日期查询
0: jdbc:phoenix:localhost:2181:/hbase> select * from "car" where "f1"."date">='20170110' and "f1"."date"<='20170210' limit 10;
+-------------------------+---------+------+-----------------+----------+-----+
| ID | coorid | cx | date | hphm | ys |
+-------------------------+---------+------+-----------------+----------+-----+
| 20170110000000_京AM7BPF | t | 丰田 | 20170110000000 | 京AM7BPF | 白 |
| 20170110000000_京C4SS4G | � | 大众 | 20170110000000 | 京C4SS4G | 红 |
| 20170110000000_新D5AGEG | � | 宝马 | 20170110000000 | 新D5AGEG | 黄 |
| 20170110000000_新DFMM96 |
| 奔驰 | 20170110000000 | 新DFMM96 | 黄 |
| 20170110000000_沪HB1R23 | � | 保时捷 | 20170110000000 | 沪HB1R23 | 银 |
| 20170110000000_沪IMARPP | � | 丰田 | 20170110000000 | 沪IMARPP | 白 |
| 20170110000000_沪KR5QA2 | � | 日产 | 20170110000000 | 沪KR5QA2 | 黑 |
| 20170110000000_浙KQDWM9 | � | 日产 | 20170110000000 | 浙KQDWM9 | 黑 |
| 20170110000000_湘BMPWCM | � | 奔驰 | 20170110000000 | 湘BMPWCM | 银 |
| 20170110000000_湘HHS4E9 | � | 日产 | 20170110000000 | 湘HHS4E9 | 红 |
+-------------------------+---------+------+-----------------+----------+-----+
10 rows selected (0.049 seconds)
用时为49毫秒。
##日期车牌查询
0: jdbc:phoenix:localhost:2181:/hbase> select * from "car" where "f1"."date">='20170110' and "f1"."date"<='20170210' and "f1"."hphm" like '.E3G%' limit 10;
+-------------------------+---------+------+-----------------+----------+-----+
| ID | coorid | cx | date | hphm | ys |
+-------------------------+---------+------+-----------------+----------+-----+
| 20170110005537_苏E3GS62 | � | 奥迪 | 20170110005537 | 苏E3GS62 | 银 |
| 20170110010344_苏E3G21Q | K | 讴歌 | 20170110010344 | 苏E3G21Q | 黄 |
| 20170110013131_苏E3G21Q | � | 讴歌 | 20170110013131 | 苏E3G21Q | 黄 |
| 20170110013318_苏E3G21Q | � | 讴歌 | 20170110013318 | 苏E3G21Q | 黄 |
| 20170110013452_苏E3GQCF | C | 沃尔沃 | 20170110013452 | 苏E3GQCF | 白 |
| 20170110034239_苏E3G7S8 | � | 丰田 | 20170110034239 | 苏E3G7S8 | 银 |
| 20170110041044_苏E3G2SA | 6 | 奔驰 | 20170110041044 | 苏E3G2SA | 黄 |
| 20170110041829_苏E3GEM2 | m | 讴歌 | 20170110041829 | 苏E3GEM2 | 蓝 |
| 20170110045503_苏E3G2SA | | 奔驰 | 20170110045503 | 苏E3G2SA | 黄 |
| 20170110050616_苏E3GS62 | O | 奥迪 | 20170110050616 | 苏E3GS62 | 银 |
+-------------------------+---------+------+-----------------+----------+-----+
10 rows selected (0.706 seconds)
用时700多毫秒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
4.索引类型
phoenix的索引大致分为两类global index和local index,好像和星环有点类似,其实这是hbase二级索引解决方案里面广为人知的两种方案,侧重点不同,使用场景也不一样。
global index,global是默认的索引格式。官方文档翻译过来的:Global indexing适用于多读少写的业务场景。使用Global indexing的话在写数据的时候会消耗大量开销,因为所有对数据表的更新操作(DELETE, UPSERT VALUES and UPSERT SELECT),会引起索引表的更新,而索引表是分布在不同的数据节点上的,跨节点的数据传输带来了较大的性能消耗。在读数据的时候Phoenix会选择索引表来降低查询消耗的时间。在默认情况下如果想查询的字段不是索引字段的话索引表不会被使用,也就是说不会带来查询速度的提升。
Local index,适用于写操作频繁的场景。与Global index一样,Phoenix会自动判定在进行查询的时候是否使用索引。使用Local indexing时,索引数据和数据表的数据是存放在相同的服务器中的避免了在写操作的时候往不同服务器的索引表中写索引带来的额外开销。使用Local indexing的时候即使查询的字段不是索引字段索引表也会被使用,这会带来查询速度的提升,这点跟Global indexing不同。一个数据表的所有索引数据都存储在一个单一的独立的可共享的表中。
官方文档上写的太书面了,下面用几个例子就能明白这两种索引格式是怎么设计以及怎样运行原理了。
4.1 global index 测试
上面的测试例子用的是global index,后面的例子为了快,就建立一些测试表。
##建立表
create table test_global(id varchar primary key,f1 varchar,f2 varchar);
##建立global index
create index test_global_index on test_global(f1) include(f2);
##插入两条数据
upsert into test_global values('1','2','3');
upsert into test_global values('4','5','6');
##查看索引表数据(注意这里查的是索引表数据,不是原始表。创建global index会生成一个索引表)
select * from test_global_index;
+-------+------+-------+
| 0:F1 | :ID | 0:F2 |
+-------+------+-------+
| 2 | 1 | 3 |
| 5 | 4 | 6 |
+-------+------+-------+
2 rows selected (0.037 seconds)
##查看hbase表上面的数据(注意这里查的是索引表在hbase中的数据)
scan 'TEST_GLOBAL_INDEX'
hbase(main):002:0> scan 'TEST_GLOBAL_INDEX'
ROW COLUMN+CELL
2\x001 column=0:\x00\x00\x00\x00, timestamp=1501489856443, value=\x00\x00\x00\x00
2\x001 column=0:\x80\x0B, timestamp=1501489856443, value=3
5\x004 column=0:\x00\x00\x00\x00, timestamp=1501489860185, value=\x00\x00\x00\x00
5\x004 column=0:\x80\x0B, timestamp=1501489860185, value=6
2 row(s) in 0.0620 seconds
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
global index
1.以上可以看出global index的设计方式,会单独写一张索引表,列族为include字段,rowkey的设计方位是:
二级索引字段1+"\x00"+二级索引字段2(复合索引)…+"\x00"+原表rowkey
2.查询的时候,会直接定位到索引表,通过rowkey查到位置,然后从索引表中带出数据
3.因为建立索引的时候还要多写一份include字段,读的时候直接从索引表定位并读出信息。所以这种表的应用场景定位是写的慢,读得快
4.2 local index 测试
##建立表
create table test_local(id varchar primary key,f1 varchar,f2 varchar);
##建立local index
create local index test_local_index on test_local(f1);
##插入两条数据
upsert into test_local values('1','2','3');
upsert into test_local values('4','5','6');
##注意:local index 并不会创建新的表,而是在原来的表里面写入索引数据
##查看hbase表中数据
hbase(main):014:0> scan 'TEST_LOCAL'
ROW COLUMN+CELL
\x00\x002\x001 column=L#0:\x00\x00\x00\x00, timestamp=1501491310774, value=\x00\x00\x00\x00
\x00\x005\x004 column=L#0:\x00\x00\x00\x00, timestamp=1501491315118, value=\x00\x00\x00\x00
1 column=0:\x00\x00\x00\x00, timestamp=1501491310774, value=x
1 column=0:\x80\x0B, timestamp=1501491310774, value=2
1 column=0:\x80\x0C, timestamp=1501491310774, value=3
4 column=0:\x00\x00\x00\x00, timestamp=1501491315118, value=x
4 column=0:\x80\x0B, timestamp=1501491315118, value=5
4 column=0:\x80\x0C, timestamp=1501491315118, value=6
4 row(s) in 0.0250 seconds
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
local index
1.以上可以看出local index的设计方式,索引数据直接写在原表rowkey中,列族不写任何实际信息,local index的rowkey的设计方位是:
原数据region的start key+"\x00"+二级索引字段1+"\x00"+二级索引字段2(复合索引)…+"\x00"+原rowkey
第一条信息"原数据region的start key",这样做的目的是保证索引数据和原数据在一个region上,定位到二级索引后根据原rowkey就可以很快在本region上获取到其它信息,减少网络开销和检索成本。
2.查询的时候,会在不同的region里面分别对二级索引字段定位,查到原rowkey后在本region上获取到其它信息
3.因为这种索引设计方式只写索引数据,省了不少空间的占用,根据索引信息拿到原rowkey后再根据rowkey到原数据里面获取其它信息。所以这种表的应用场景定位是写的快,读得慢
4.3 local index和global index比较
1.索引数据
global index单独把索引数据存到一张表里,保证了原始数据的安全,侵入性小
local index把数据写到原始数据里面,侵入性强,原表的数据量=原始数据+索引数据,使原始数据更大
2.性能方面
global index要多写出来一份数据,写的压力就大一点,但读的速度就非常快
local index只用写一份索引数据,节省不少空间,但多了一步通过rowkey查找数据,写的速度非常快,读的速度就没有直接取自己的列族数据快。
4.4其它
phoenix大的方面分为这两种索引,细的方面可以分为4种。phoenix还另外加了一种配置,根据索引是否可变,意思就是为了减少写入的压力,索引表只建立一次不会更改。
5.总结
用phoenix只是用hbase自身实现了hbase自己的二级索引,用hbase自己rowkey查询的特点来设计索引数据的rowkey,性能方面完全要靠一次检索索引数据的数据量大小了,所以具体业务具体分析。