当前位置:   article > 正文

Phoenix(三)Phoenix的二级索引_phoenix创建二级索引

phoenix创建二级索引

一、概要

目前HBASE只有基于字典序的主键索引,对于非主键过滤条件的查询都会变成扫全表操作,为了解决这个问题Phoenix引入了二级索引功能。然而此二级索引又有别于传统关系型数据库的二级索引,本文将详细描述了Phoenix中二级索引功能、用法和原理。

二、二级索引

示例表如下(为了能够容易通过HBASE SHELL对照表内容,我们对属性值COLUMN_ENCODED_BYTES设置为0,不对column family进行编码):

  1. CREATE TABLE TEST (
  2. ID VARCHAR NOT NULL PRIMARY KEY,
  3. COL1 VARCHAR,
  4. COL2 VARCHAR
  5. ) COLUMN_ENCODED_BYTES=0;
  6. upsert into TEST values('1', '2', '3');

1. 全局索引

全局索引更多的应用在读较多的场景。它对应一张独立的HBASE表。对于全局索引,在查询中检索的列如果不在索引表中,默认的索引表将不会被使用,除非使用hint。

创建全局索引:

CREATE INDEX IDX_COL1 ON TEST(COL1)

通过HBASE SHELL观察生成的索引表IDX_COL1。我们发现全局索引表的RowKey存储了索引列的值和原表RowKey的值,这样编码更有利于提高查询的性能。

  1. hbase(main):001:0> scan 'IDX_COL1'
  2. ROW COLUMN+CELL
  3. 2\x001 column=0:_0, timestamp=1520935113031, value=x
  4. 1 row(s) in 0.1650 seconds

实际上全局索引的RowKey将会按照如下格式进行编码。
Screen_Shot_2018_03_13_at_18_04_32

  • SALT BYTE: 全局索引表和普通phoenix表一样,可以在创建索引时指定SALT_BUCKETS或者split key。此byte正是存储着salt。
  • TENANT_ID: 当前数据对应的多租户ID。
  • INDEX VALUE: 索引数据。
  • PK VALUE: 原表的RowKey。

2. 本地索引

因为本地索引和原数据是存储在同一个表中的,所以更适合写多的场景。对于本地索引,查询中无论是否指定hint或者是查询的列是否都在索引表中,都会使用索引表。

创建本地索引:

create local index LOCAL_IDX_COL1 ON TEST(COL1);

通过HBASE SHELL观察表'TEST', 我们可以看到表中多了一行column为L#0:_0的索引数据。

  1. hbase(main):001:0> scan 'TEST'
  2. ROW COLUMN+CELL
  3. \x00\x002\x001 column=L#0:_0, timestamp=1520935997600, value=_0
  4. 1 column=0:COL1, timestamp=1520935997600, value=2
  5. 1 column=0:COL2, timestamp=1520935997600, value=3
  6. 1 column=0:_0, timestamp=1520935997600, value=x
  7. 2 row(s) in 0.1680 seconds

本地索引的RowKey将会按照如下格式进行编码:
Screen_Shot_2018_03_13_at_20_16_24

  • REGION START KEY : 当前row所在region的start key。加上这个start key的好处是,可以让索引数据和原数据尽量在同一个region, 减小IO,提升性能。
  • INDEX ID : 每个ID对应不同的索引表。
  • TENANT ID :当前数据对应的多租户ID。
  • INDEX VALUE: 索引数据。
  • PK VALUE: 原表的RowKey。

3. 覆盖索引

覆盖索引的特点是把原数据存储在索引数据表中,这样在查询到索引数据时就不需要再次返回到原表查询,可以直接拿到查询结果。

创建覆盖索引:

create  index IDX_COL1_COVER_COL2 on TEST(COL1) include(COL2);

通过HBASE SHELL 查询表IDX_COL1_COVER_COL2, 我们发现include的列的值被写入到了value中。

  1. hbase(main):003:0> scan 'IDX_COL1_COVER_COL2'
  2. ROW COLUMN+CELL
  3. 2\x001 column=0:0:COL2, timestamp=1520943893821, value=3
  4. 2\x001 column=0:_0, timestamp=1520943893821, value=x
  5. 1 row(s) in 0.0180 seconds

对于类似select col2 from TEST where COL1='2'的查询,查询一次索引表就能获得结果。其查询计划如下:

  1. +--------------------------------------------------------------------------------------+-----------------+----------------+---+
  2. | PLAN | EST_BYTES_READ | EST_ROWS_READ | E |
  3. +--------------------------------------------------------------------------------------+-----------------+----------------+---+
  4. | CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN RANGE SCAN OVER IDX_COL1_COVER_COL2 ['2'] | null | null | n |
  5. +--------------------------------------------------------------------------------------+-----------------+----------------+---+

4. 函数索引

函数索引的特点是能根据表达式创建索引,适用于对查询表,过滤条件是表达式的表创建索引。例如:

  1. //创建函数索引
  2. CREATE INDEX CONCATE_IDX ON TEST (UPPER(COL1||COL2))
  3. //查询函数索引
  4. SELECT * FROM TEST WHERE UPPER(COL1||COL2)='23'

三、什么是Phoenix的二级索引?

Phoenix的二级索引我们基本上已经介绍过了,我们回过头来继续看Phoenix二级索引的官方定义:Secondary indexes are an orthogonal way to access data from its primary access path。通过以下例子我们再理解下这个定义。

  1. 对表TESTCOL1创建全局索引
CREATE INDEX IDX_COL1 ON TEST(COL1);
  1. 查询所有字段。
select * from TEST where COL1='2';

以上查询的查询计划如下:

  1. +----------------------------------------------------------------+-----------------+----------------+--------------+
  2. | PLAN | EST_BYTES_READ | EST_ROWS_READ | EST_INFO_TS |
  3. +----------------------------------------------------------------+-----------------+----------------+--------------+
  4. | CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN FULL SCAN OVER TEST | null | null | null |
  5. | SERVER FILTER BY COL1 = '2' | null | null | null |
  6. +----------------------------------------------------------------+-----------------+----------------+--------------+
  1. 查询id字段:
select id from TEST where  COL1='2';

查询计划如下

  1. +---------------------------------------------------------------------------+-----------------+----------------+--------------+
  2. | PLAN | EST_BYTES_READ | EST_ROWS_READ | EST_INFO_TS |
  3. +---------------------------------------------------------------------------+-----------------+----------------+--------------+
  4. | CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN RANGE SCAN OVER IDX_COL1 ['2'] | null | null | null |
  5. | SERVER FILTER BY FIRST KEY ONLY | null | null | null |
  6. +---------------------------------------------------------------------------+-----------------+----------------+--------------+

两个查询都没有通过hint强制指定索引表,查询计划显示,查询所有字段时发生了需要极力避免的扫全表操作(一般数据量在几十万级别的扫全表很容易造成集群不稳定),而查询id时利用了索引表。从现象来看,当查询中出现的字段都在索引表中时(可以是索引字段或者数据表主键,也可以是覆盖索引字段),会自动走索引表,否则查询会退化为全表扫描。

在我们实际应用中一个数据表会有多个索引表,为了能让我们的查询使用合理的索引表,目前都需要通过Hint去指定。

四、全局索引设计实践

全局索引说明

全局索引的根本是通过单独的HBase表来存储数据表的索引数据。我们通过如下示例看索引数据和主表数据的关系。

  1. -- 创建数据表
  2. CREATE TABLE DATA_TABLE(
  3. A VARCHAR PRIMARY KEY,
  4. B VARCHAR,
  5. C INTEGER
  6. D INTEGER);
  7. -- 创建索引
  8. CREATE INDEX B_IDX ON DATA_TABLE(B)INCLUDE(C);
  9. -- 插入数据
  10. UPSERT INTO DATA_TABLE VALUES('A','B',1,2);

当写入数据到主表时,索引数据也会被同步到索引表中。索引表中的主键将会是索引列和数据表主键的组合值,include的列被存储在索引表的普通列中,其目的是让查询更加高效,只需要查询一次索引表就能够拿到数据,而不用去回查主表。其过程入下图:
image

Phoenix表就是HBase表,而HBase Rowkey都是通过二进制数据的字典序排列存储,也就意味着Row key前缀匹配度越高就越容易排在一起。

全局索引设计

我们继续使用DATA_TABLE作为示例表,创建如下组合索引。之前我们已经提到索引表中的Row key是字典序存储的,什么样的查询适合这样的索引结构呢?

CREATE INDEX B_C_D_IDX ON DATA_TABLE(B,C,D);
所有字段条件以=操作符为例:

image

注:上表查询中and条件不一定要和索引组合字段顺序一致,可以任意组合。

在实际使用中我们也只推荐使用1~4,遵循前缀匹配原则,避免触发扫全表。5~7条件就要扫描全表数据才能过滤出来符合这些条件的数据,所以是极力不推荐的。

其它

  • 对于order by字段或者group by字段仍然能够使用二级索引字段来加速查询。
  • 尽量通过合理的设计数据表的主键规避建更多的索引表,因为索引表越多写放大越严重。
  • 使用了ROW_TIMESTAMP特性后不能使用全局索引
  • 对索引表适当的使用加盐特性能提升查询写入性能,避免热点。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/656681
推荐阅读