《Phoenix使用总结》 三、二级索引

二级索引

HBase 二级索引方案

HBase 中的行是以 RowKey 的字典序排序存储。所以对于 HBase 的快速查询主要有两种场景,一种是根据 RowKey 快速查询单行数据,另一种是基于 RowKey 的前缀查询。由于 HBase 只有 RowKey 的一级索引,根据其他的字段进行查询效率是很低的,实际业务场景中可能需要引入二级索引。

我所了解的 HBase 二级索引的解决思路有如下几种:

  • 官方的解决方案是使用 Coprocessor(类似触发器),自定义写入逻辑,当有数据写入时同时写入一份索引表。
  • 可以使用其他的工具在外部构建和维护索引关系,索引字段和 RowKey 的对应关系,比如使用 Elasticsearch、MongoDB 等。
  • 使用 Phoenix 构建二级索引。

Coprocessor 的方案由于运行都交给了 Regionserver,侵入性比较强,会对 Regionserver 的性能造成影响。第一版的时候我使用了 MongoDB 维护了索引关系,后期数据量达到 20 亿,对 MongoDB 所在的服务器造成了很大的压力,所以后期决定使用基于 HBase 的 Phoenix 构建二级索引。

Phoenix 二级索引

官方文档:http://phoenix.apache.org/secondary_indexing.html

索引特性

Covered Indexes(覆盖索引)

覆盖索引指的是把查询相关的字段都指定在索引中,这样查询就只需要在索引表中查询就行。建立索引的语句如下,把查询的字段(where条件字段)放在 ON 语句中,把其他需要查询出来的字段(其他 select 字段)放在 INCLUDE 中。

Phoenix 的索引其实就是维护一张 HBase 表(本地索引与原始数据同一张表),把索引的字段作为 RowKey,把 INCLUDE 的字段放在 COLUMN 中。所以索引的顺序也注意,需要把条件中一定有的字段放在前面,可能有的字段放在后面。

1
2
CREATE INDEX my_index ON my_table (v1,v2) INCLUDE(v3);
SELECT v1, v2, v3 FROM my_table WHERE v1='' AND v2=''; // where 条件中 v1、v2 字段放在 ON 语句中,其他查询字段 v3 放在 INCLUDE 语句中

Functional Indexes(函数索引)

索引的字段不局限与列,支持任意的表达式来创建索引。

1
2
CREATE INDEX UPPER_NAME_IDX ON EMP (UPPER(FIRST_NAME||' '||LAST_NAME));
SELECT EMP_ID FROM EMP WHERE UPPER(FIRST_NAME||' '||LAST_NAME)='JOHN DOE'; // 使用函数索引,生成大写的 FIRST_NAME + LAST_NAME 的索引

构建索引

Phoenix 支持两种类型的索引机制,Global Indexes(全局索引) 和 Local Indexes(本地索引),不同的索引机制适用于不同的业务场景,默认是构建全局索引。全局索引是维护了一张新的索引表,本地索引的索引数据存在了原始数据表中(4.8.0之后)。

Global Indexes(全局索引)

Global Indexes(全局索引) 适用于大量读取操作的场景。所有数据的更新和写操作都需要更新相关的索引表,所以开销主要发生在写入阶段,查询是直接查询索引表,所以查询开销较小。由于只会查询索引表,所以在建立索引时需要合理设计 INCLUDE 字段,把需要查询的字段都放进去。

Local Indexes(本地索引)

Local Indexes(本地索引) 适用于大量写入操作的场景。本地索引的索引数据放在了数据表中(从4.8.0开始,我们将所有本地索引数据存储在同一数据表中的单独阴影列族中),索引数据和原始数据是在同一台机器上面,所以没有额外的网络开销。本地索引可以使用在没有全面覆盖 INCLUDE 的场景。

索引实例

异步创建索引

1
2
3
4
5
6
7
8
9
10
11
12
13
# 先创建异步索引
CREATE INDEX async_index ON my_schema.my_table (v) ASYNC

# 再通过 MapReduce 任务异步构建索引,output-path 是一个hdfs路径(存放 MapReduce 任务执行的临时文件)
${HBASE_HOME}/bin/hbase org.apache.phoenix.mapreduce.index.IndexTool
--schema my_schema --data-table my_table --index-table async_index
--output-path ASYNC_IDX_HFILES

# 实例
CREATE LOCAL INDEX match_index_new_qm ON PUBG.MATCH_INDEX_NEW(lowerNickName, type, mode, queueSize, startedAt DESC) INCLUDE (matchId, totalRank) ASYNC
# 构建时发现无法找到 org.apache.phoenix.mapreduce.index.IndexTool 类,索引把 jar 加到 HBASE_CLASSPATH 中
export HBASE_CLASSPATH=/opt/cloudera/parcels/APACHE_PHOENIX/lib/phoenix/phoenix-4.14.0-cdh5.13.2-client.jar
hbase org.apache.phoenix.mapreduce.index.IndexTool --schema PUBG --data-table MATCH_INDEX_NEW --index-table match_index_new_qm --output-path /user/hadoop/wangzhen

创建索引

1
2
3
4
CREATE INDEX my_index ON my_table (v1) INCLUDE (v2);

CREATE INDEX my_index ON my_table (v2 DESC, v1) INCLUDE (v3)
SALT_BUCKETS=10, DATA_BLOCK_ENCODING='NONE';

与CREATE TABLE语句一样,CREATE INDEX语句可以传递属性以应用于基础HBase表,包括对其进行加盐的能力,如果主表被加盐,则索引将以相同的方式自动为全局索引加盐。使用本地索引时,不允许指定SALT_BUCKETS。

创建本地索引

1
CREATE LOCAL INDEX my_index ON my_table (v1)

查询时指定索引

1
SELECT /*+ INDEX(my_table my_index) */ v2 FROM my_table WHERE v1 = 'foo'