摘要: 目前HBASE只有基於字典序的主鍵索引,對於非主鍵過濾條件的查詢都會變成掃全表操作,為了解決這個問題Phoenix引入了二級索引功能。然而此二級索引又有別於傳統關系型數據庫的二級索引,本文將詳細描述Phoenix中二級索引功能、用法和原理,希望能夠對大家在業務技術選型時起到一些幫助作用。
一、概要
目前HBASE只有基於字典序的主鍵索引,對於非主鍵過濾條件的查詢都會變成掃全表操作,為了解決這個問題Phoenix引入了二級索引功能。然而此二級索引又有別於傳統關系型數據庫的二級索引,本文將詳細描述Phoenix中二級索引功能、用法和原理,希望能夠對大家在業務技術選型時起到一些幫助作用。
二、二級索引
示例表如下(為了能夠容易通過HBASE SHELL對照表內容,我們對屬性值COLUMN_ENCODED_BYTES設置為0,不對column family進行編碼):
CREATE TABLE TEST ( ID VARCHAR NOT NULL PRIMARY KEY, COL1 VARCHAR, COL2 VARCHAR ) COLUMN_ENCODED_BYTES=0; upsert into TEST values('1', '2', '3');
1. 全局索引
全局索引更多的應用在讀較多的場景。它對應一張獨立的HBASE表。對於全局索引,在查詢中檢索的列如果不在索引表中,默認的索引表將不會被使用,除非使用hint。
創建全局索引:
CREATE INDEX IDX_COL1 ON TEST(COL1)
通過HBASE SHELL觀察生成的索引表IDX_COL1。我們發現全局索引表的RowKey存儲了索引列的值和原表RowKey的值,這樣編碼更有利於提高查詢的性能。
hbase(main):001:0> scan 'IDX_COL1' ROW COLUMN+CELL 2\x001 column=0:_0, timestamp=1520935113031, value=x 1 row(s) in 0.1650 seconds
實際上全局索引的RowKey將會按照如下格式進行編碼。
- 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的索引數據。
hbase(main):001:0> scan 'TEST' ROW COLUMN+CELL \x00\x002\x001 column=L#0:_0, timestamp=1520935997600, value=_0 1 column=0:COL1, timestamp=1520935997600, value=2 1 column=0:COL2, timestamp=1520935997600, value=3 1 column=0:_0, timestamp=1520935997600, value=x 2 row(s) in 0.1680 seconds
本地索引的RowKey將會按照如下格式進行編碼:
- 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中。
hbase(main):003:0> scan 'IDX_COL1_COVER_COL2' ROW COLUMN+CELL 2\x001 column=0:0:COL2, timestamp=1520943893821, value=3 2\x001 column=0:_0, timestamp=1520943893821, value=x 1 row(s) in 0.0180 seconds
對於類似select col2 from TEST where COL1='2'的查詢,查詢一次索引表就能獲得結果。其查詢計划如下:
+--------------------------------------------------------------------------------------+-----------------+----------------+---+
| PLAN | EST_BYTES_READ | EST_ROWS_READ | E | +--------------------------------------------------------------------------------------+-----------------+----------------+---+ | CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN RANGE SCAN OVER IDX_COL1_COVER_COL2 ['2'] | null | null | n | +--------------------------------------------------------------------------------------+-----------------+----------------+---+
4. 函數索引
函數索引的特點是能根據表達式創建索引,適用於對查詢表,過濾條件是表達式的表創建索引。例如:
//創建函數索引
CREATE INDEX CONCATE_IDX ON TEST (UPPER(COL1||COL2)) //查詢函數索引 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。簡單理解為,在主訪問路徑(通過row key訪問)上發生正交的一種方法,更清楚的應該描述為:索引列訪問和row key訪問產生交集時的一種索引方法。我們來通過一個例子說明:
1. 對表TEST的COL1創建全局索引
CREATE INDEX IDX_COL1 ON TEST(COL1);
2. 對於如下查詢必將發生FULL SCAN。
select * from TEST where COL1='2';
以上查詢的查詢計划如下:
+----------------------------------------------------------------+-----------------+----------------+--------------+
| PLAN | EST_BYTES_READ | EST_ROWS_READ | EST_INFO_TS |
+----------------------------------------------------------------+-----------------+----------------+--------------+
| CLIENT 1-CHUNK PARALLEL 1-WAY ROUND ROBIN FULL SCAN OVER TEST | null | null | null | | SERVER FILTER BY COL1 = '2' | null | null | null | +----------------------------------------------------------------+-----------------+----------------+--------------+
3. 對於以下查詢將會形成點查。因為二級索引是RowKey的交集。
select * from TEST where id='1' and COL1='2'
查詢計划如下
+---------------------------------------------------------------------------------------------+-----------------+-------------+
| PLAN | EST_BYTES_READ | EST_ROWS_RE | +---------------------------------------------------------------------------------------------+-----------------+-------------+ | CLIENT 1-CHUNK 1 ROWS 203 BYTES PARALLEL 1-WAY ROUND ROBIN POINT LOOKUP ON 1 KEY OVER TEST | 203 | 1 | | SERVER FILTER BY COL1 = '2' | 203 | 1 | +---------------------------------------------------------------------------------------------+-----------------+-------------+
對於2中所描述的查詢為什么會發生FULL SCAN? 正如Phoenix二級索引官方定義的一樣,因為“沒有和RowKey列的查詢發生正交關系”,除非使用Hint強制指定索引表。
四、索引Building
Phoenix的二級索引創建有同步和異步兩種方式。
- 在執行
CREATE INDEX IDX_COL1 ON TEST(COL1)時會進行索引數據的同步。此方法適用於數據量較小的情況。 - 異步build索引需要借助MR,創建異步索引語法和同步索引相差一個關鍵字:
ASYNC。
//創建異步索引
CREATE INDEX ASYNC_IDX ON DB.TEST (COL1) ASYNC //build 索引數據 ${HBASE_HOME}/bin/hbase org.apache.phoenix.mapreduce.index.IndexTool --schema DB --data-table TEST --index-table ASYNC_IDX --output-path ASYNC_IDX_HFILES
五、索引問題匯總
1. 創建同步索引超時怎么辦?
在客戶端配置文件hbase-site.xml中,把超時參數設置大一些,足夠build索引數據的時間。
<property> <name>hbase.rpc.timeout</name> <value>60000000</value> </property> <property> <name>hbase.client.scanner.timeout.period</name> <value>60000000</value> </property> <property> <name>phoenix.query.timeoutMs</name> <value>60000000</value> </property>
2. 索引表最多可以創建多少個?
建議不超過10個
3. 為什么索引表多了,單條寫入會變慢?
索引表越多寫放大越嚴重。寫放大情況可以參考下圖。

References
- https://phoenix.apache.org/secondary_indexing.html
- https://community.hortonworks.com/articles/61705/art-of-phoenix-secondary-indexes.html
轉自:https://yq.aliyun.com/articles/536850
交流
如果大家對HBase有興趣,致力於使用HBase解決實際的問題,歡迎加入Hbase技術社區群交流:
微信HBase技術社區群,假如微信群加不了,可以加秘書微信: SH_425 ,然后邀請您。
釘釘HBase技術社區群

