為什么需要Secondary Index
對于HBase而言,如果想精確地定位到某行記錄根穷,唯一的辦法是通過rowkey來查詢仅醇。如果不通過rowkey來查找數(shù)據(jù),就必須逐行地比較每一列的值兽赁,即全表掃瞄。對于較大的表冷守,全表掃瞄的代價(jià)是不可接受的刀崖。
但是,很多情況下拍摇,需要從多個(gè)角度查詢數(shù)據(jù)亮钦。例如,在定位某個(gè)人的時(shí)候充活,可以通過姓名蜂莉、身份證號孙咪、學(xué)籍號等不同的角度來查詢,要想把這么多角度的數(shù)據(jù)都放到rowkey中幾乎不可能(業(yè)務(wù)的靈活性不允許巡语,對rowkey長度的要求也不允許)。
所以淮菠,需要secondary index來完成這件事男公。secondary index的原理很簡單,但是如果自己維護(hù)的話則會(huì)麻煩一些『狭辏現(xiàn)在枢赔,Phoenix已經(jīng)提供了對HBase secondary index的支持,下面將說明這樣用Phoenix來在HBase中創(chuàng)建二級索引拥知。
配置HBase以支持Secondary Index
在每一個(gè)RegionServer的hbase-site.xml中加入如下的屬性:
<property>
<name>hbase.regionserver.wal.codec</name>
<value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
</property>
<property>
<name>hbase.region.server.rpc.scheduler.factory.class</name>
<value>org.apache.hadoop.hbase.ipc.PhoenixRpcSchedulerFactory</value>
<description>Factory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates</description>
</property>
<property>
<name>hbase.rpc.controllerfactory.class</name>
<value>org.apache.hadoop.hbase.ipc.controller.ServerRpcControllerFactory</value>
<description>Factory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates</description>
</property>
<property>
<name>hbase.coprocessor.regionserver.classes</name>
<value>org.apache.hadoop.hbase.regionserver.LocalIndexMerger</value>
</property>
在每一個(gè)Master的hbase-site.xml中加入如下的屬性:
<property>
<name>hbase.master.loadbalancer.class</name>
<value>org.apache.phoenix.hbase.index.balancer.IndexLoadBalancer</value>
</property>
<property>
<name>hbase.coprocessor.master.classes</name>
<value>org.apache.phoenix.hbase.index.master.IndexMasterObserver</value>
</property>
Global Indexing v.s. Local Indexing
Global Indexing
Global indexing targets read heavy, low write uses cases. With global indexes, all the performance penalties for indexes occur at write time. We intercept the data table updates on write (DELETE
, UPSERT VALUES
and UPSERT SELECT
), build the index update and then sent any necessary updates to all interested index tables. At read time, Phoenix will select the index table to use that will produce the fastest query time and directly scan it just like any other HBase table. By default, unless hinted, an index will not be used for a query that references a column that isn’t part of the index.
Local Indexing
Local indexing targets write heavy, space constrained use cases. Just like with global indexes, Phoenix will automatically select whether or not to use a local index at query-time. With local indexes, index data and table data co-reside on same server preventing any network overhead during writes. Local indexes can be used even when the query isn’t fully covered (i.e. Phoenix automatically retrieve the columns not in the index through point gets against the data table). Unlike global indexes, all local indexes of a table are stored in a single, separate shared table. At read time when the local index is used, every region must be examined for the data as the exact region location of index data cannot be predetermined. Thus some overhead occurs at read-time.
翻譯
Global Indexing
Global indexing踏拜,全局索引,適用于讀多寫少的業(yè)務(wù)場景低剔。使用Global indexing在寫數(shù)據(jù)的時(shí)候開銷很大速梗,因?yàn)樗袑?shù)據(jù)表的更新操作(DELETE, UPSERT VALUES and UPSERT SELECT),都會(huì)引起索引表的更新襟齿,而索引表是分布在不同的數(shù)據(jù)節(jié)點(diǎn)上的姻锁,跨節(jié)點(diǎn)的數(shù)據(jù)傳輸帶來了較大的性能消耗。在讀數(shù)據(jù)的時(shí)候Phoenix會(huì)選擇索引表來降低查詢消耗的時(shí)間猜欺。在默認(rèn)情況下如果想查詢的字段不是索引字段的話索引表不會(huì)被使用位隶,也就是說不會(huì)帶來查詢速度的提升。
Local Indexing
Local indexing开皿,本地索引涧黄,適用于寫操作頻繁以及空間受限制的場景。與Global indexing一樣赋荆,Phoenix會(huì)自動(dòng)判定在進(jìn)行查詢的時(shí)候是否使用索引笋妥。使用Local indexing時(shí),索引數(shù)據(jù)和數(shù)據(jù)表的數(shù)據(jù)存放在相同的服務(wù)器中糠睡,這樣避免了在寫操作的時(shí)候往不同服務(wù)器的索引表中寫索引帶來的額外開銷挽鞠。使用Local indexing的時(shí)候即使查詢的字段不是索引字段索引表也會(huì)被使用,這會(huì)帶來查詢速度的提升狈孔,這點(diǎn)跟Global indexing不同信认。對于Local Indexing,一個(gè)數(shù)據(jù)表的所有索引數(shù)據(jù)都存儲在一個(gè)單一的獨(dú)立的可共享的表中均抽。
Immutable index And Mutable index
immutable index
immutable index嫁赏,不可變索引,適用于數(shù)據(jù)只增加不更新并且按照時(shí)間先后順序存儲(time-series data)的場景油挥,如保存日志數(shù)據(jù)或者事件數(shù)據(jù)等潦蝇。不可變索引的存儲方式是write one款熬,append only。當(dāng)在Phoenix使用create table語句時(shí)指定IMMUTABLE_ROWS = true表示該表上創(chuàng)建的索引將被設(shè)置為不可變索引攘乒。Phoenix默認(rèn)情況下如果在create table時(shí)不指定IMMUTABLE_ROW = true時(shí)者冤,表示該表為mutable俄周。不可變索引分為Global immutable index和Local immutable index兩種。
mutable index
mutable index,可變索引摇予,適用于數(shù)據(jù)有增刪改的場景勃教。Phoenix默認(rèn)情況創(chuàng)建的索引都是可變索引宾符,除非在create table的時(shí)候顯式地指定IMMUTABLE_ROWS = true藻烤。可變索引同樣分為Global immutable index和Local immutable index兩種爽雄。
特別注意】
如果使用的是CDH部署的HBase蝠检,需要在Cloudera Manager管理頁面里面的HBase“配置”頁面里的hbase-site.xml項(xiàng)增加上述配置,并在管理頁面里面重啟HBase才能使得配置生效挚瘟。
master
regenServer
5. 測試案例
5.1 測試案例1
HBase 1000w數(shù)據(jù)
users_test表
【Global Indexing】
在沒有創(chuàng)建二級索引之前查詢特定USER_NAME的用戶信息耗時(shí)大約16s左右叹谁。
在USER_NAME列上面創(chuàng)建二級索引:
create index USERS_TEST_IDX0 on "users_test ("info".USER_NAME)
創(chuàng)建二級索引后查詢特定USER_NAME的用戶名稱耗時(shí)為ms級別
【說明】 可以通過explain命令來查看查詢是否用到二級索引
【注意】 如果在select條件里面選擇了其他的列,如USER_NO刽沾,因?yàn)樵摿袥]有存在于索引表本慕,因此查詢不會(huì)走索引表。
如果想在select USER_NAME,USER_NO查詢?nèi)匀蛔咚饕嗬欤仨殑?chuàng)建如下索引:
- 方式一锅尘,采取INCLUDE(index cover,即索引覆蓋)的方式:
create index USERS_TEST_IDX1 on "users_test"("info".USER_NAME) INCLUDE("info".USER_NO)
索引覆蓋其實(shí)就是將INCLUDE里面包含的列都存儲到索引表里面布蔗,當(dāng)檢索的時(shí)候就可以從索引表里直接帶回這些列值藤违。要特別注意索引列和索引覆蓋列的區(qū)別,索引列在索引表里面是以rowkey的形式存在纵揍,多個(gè)索引列以某個(gè)約定的字節(jié)分割然后一起存儲在rowkey里面顿乒,也就是說當(dāng)索引列有很多個(gè)的時(shí)候,rowkey的長度也相應(yīng)會(huì)變長泽谨,大小取決于索引列值的大小璧榄。而索引覆蓋列,是存儲在索引表的列族中吧雹。
- 方式二骨杂,采取多列索引:
create index USERS_TEST_IDX2 on "users_test"("info".USER_NAME, "info".USER_NO)
【說明】
多列索引在滿足前綴式的情況才會(huì)用到,如創(chuàng)建了A,B,C順序的多列索引雄卷,當(dāng)在where條件指定A條件搓蚪、A B條件或者A B C條件均會(huì)走索引,但是 B C條件則無法走索引丁鹉。
【Local Indexing】
在users_test表創(chuàng)建local index類型的二級索引:
create local index USERS_TEST_LOCAL_IDX ON "users_test"("info".USER_NAME)
與Global Indexing不同的是妒潭,如果select子句里面帶有除了索引列(USER_NAME)以外的列悴能,仍然可以走索引表。
【說明】
創(chuàng)建Local Indexing時(shí)候指定的索引名稱會(huì)與實(shí)際創(chuàng)建在Hbase里面的表名稱不一致雳灾,這應(yīng)該是Phoenix做了映射的關(guān)系漠酿,而且對于同一個(gè)Hbase里面的table創(chuàng)建多個(gè)Local Indexing,索引表在Hbse list命令查詢的時(shí)候也只有一個(gè)谎亩。
5.2 測試案例2
HBase 1e數(shù)據(jù)
ammeter_test表
【Global Indexing】
create index AMMETER_TEST_IDX
on AMMETER_TEST ("info"."ammeter_no1", "info"."ammeter_no2") include("info"."ammeter_price");
(1) 條件查詢包含rowkey
> explain select * from AMMETER_TEST where "info"."ammeter_no1" = '11000000005281' AND "ammeter_no2" = '11000000001004' and ROW = '11000002310462'
> select * from AMMETER_TEST where "info"."ammeter_no1" = '11000000005281' AND "ammeter_no2" = '11000000001004' and ROW = '11000002310462'
(2) 條件查詢不包含rowkey但滿足二級索引查找條件
> explain select ROW,"ammeter_price" from AMMETER_TEST where "info"."ammeter_no1" = '11000000005281' and "ammeter_no2" = '11000000001004'
> select ROW,"ammeter_price" from AMMETER_TEST where "info"."ammeter_no1" = '11000000005281' and "ammeter_no2" = '11000000001004' LIMIT 5
【分析】
- 對于包含rowkey的條件查詢记靡,Phoenix會(huì)啟用服務(wù)器端過濾器快速篩選匹配的行并返回,億級數(shù)據(jù)也能達(dá)到毫秒級別響應(yīng)团驱。
- 對于沒有包含rowkey的條件查詢,如果條件滿足Phoenix二級索引查找空凸,Phoenix會(huì)查二級索引表并快速返回記錄嚎花。
6. 同步創(chuàng)建索引與異步創(chuàng)建索引
前面所講的創(chuàng)建索引為同步創(chuàng)建索引,當(dāng)執(zhí)行create index的時(shí)候呀洲,索引表會(huì)直接與源數(shù)據(jù)表進(jìn)行同步紊选。但是,有時(shí)候我們的源表數(shù)據(jù)量很大道逗,同步創(chuàng)建索引會(huì)拋出異常兵罢。異常信息大致如下所示:
15/12/11 14:20:08 WARN client.ScannerCallable: Ignore, probably already closed
org.apache.hadoop.hbase.UnknownScannerException: org.apache.hadoop.hbase.UnknownScannerException: Name: 37, already closed?
at org.apache.hadoop.hbase.regionserver.RSRpcServices.scan(RSRpcServices.java:2092)
at org.apache.hadoop.hbase.protobuf.generated.ClientProtos$ClientService$2.callBlockingMethod(ClientProtos.java:31443)
at org.apache.hadoop.hbase.ipc.RpcServer.call(RpcServer.java:2035)
at org.apache.hadoop.hbase.ipc.CallRunner.run(CallRunner.java:107)
at org.apache.hadoop.hbase.ipc.RpcExecutor.consumerLoop(RpcExecutor.java:130)
at org.apache.hadoop.hbase.ipc.RpcExecutor$1.run(RpcExecutor.java:107)
at java.lang.Thread.run(Thread.java:745)
at sun.reflect.GeneratedConstructorAccessor13.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
at org.apache.hadoop.ipc.RemoteException.instantiateException(RemoteException.java:106)
at org.apache.hadoop.ipc.RemoteException.unwrapRemoteException(RemoteException.java:95)
at org.apache.hadoop.hbase.protobuf.ProtobufUtil.getRemoteException(ProtobufUtil.java:313)
at org.apache.hadoop.hbase.client.ScannerCallable.close(ScannerCallable.java:329)
at org.apache.hadoop.hbase.client.ScannerCallable.call(ScannerCallable.java:184)
at org.apache.hadoop.hbase.client.ScannerCallableWithReplicas.call(ScannerCallableWithReplicas.java:136)
at org.apache.hadoop.hbase.client.ScannerCallableWithReplicas.call(ScannerCallableWithReplicas.java:56)
at org.apache.hadoop.hbase.client.RpcRetryingCaller.callWithoutRetries(RpcRetryingCaller.java:200)
at org.apache.hadoop.hbase.client.ClientScanner.call(ClientScanner.java:288)
at org.apache.hadoop.hbase.client.ClientScanner.close(ClientScanner.java:507)
at org.apache.phoenix.iterate.ScanningResultIterator.close(ScanningResultIterator.java:49)
at org.apache.phoenix.iterate.TableResultIterator.close(TableResultIterator.java:95)
at org.apache.phoenix.jdbc.PhoenixResultSet.close(PhoenixResultSet.java:162)
at org.apache.phoenix.compile.UpsertCompiler.upsertSelect(UpsertCompiler.java:199)
at org.apache.phoenix.compile.UpsertCompiler.access$000(UpsertCompiler.java:114)
at org.apache.phoenix.compile.UpsertCompiler$UpsertingParallelIteratorFactory.mutate(UpsertCompiler.java:229)
at org.apache.phoenix.compile.MutatingParallelIteratorFactory.newIterator(MutatingParallelIteratorFactory.java:62)
at org.apache.phoenix.iterate.ParallelIterators$1.call(ParallelIterators.java:109)
at org.apache.phoenix.iterate.ParallelIterators$1.call(ParallelIterators.java:100)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at org.apache.phoenix.job.JobManager$InstrumentedJobFutureTask.run(JobManager.java:183)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.apache.hadoop.hbase.ipc.RemoteWithExtrasException(org.apache.hadoop.hbase.UnknownScannerException): org.apache.hadoop.hbase.UnknownScannerException: Name: 37, already closed?
at org.apache.hadoop.hbase.regionserver.RSRpcServices.scan(RSRpcServices.java:2092)
at org.apache.hadoop.hbase.protobuf.generated.ClientProtos$ClientService$2.callBlockingMethod(ClientProtos.java:31443)
at org.apache.hadoop.hbase.ipc.RpcServer.call(RpcServer.java:2035)
at org.apache.hadoop.hbase.ipc.CallRunner.run(CallRunner.java:107)
at org.apache.hadoop.hbase.ipc.RpcExecutor.consumerLoop(RpcExecutor.java:130)
at org.apache.hadoop.hbase.ipc.RpcExecutor$1.run(RpcExecutor.java:107)
at java.lang.Thread.run(Thread.java:745)
at org.apache.hadoop.hbase.ipc.RpcClientImpl.call(RpcClientImpl.java:1199)
at org.apache.hadoop.hbase.ipc.AbstractRpcClient.callBlockingMethod(AbstractRpcClient.java:216)
at org.apache.hadoop.hbase.ipc.AbstractRpcClient$BlockingRpcChannelImplementation.callBlockingMethod(AbstractRpcClient.java:300)
at org.apache.hadoop.hbase.protobuf.generated.ClientProtos$ClientService$BlockingStub.scan(ClientProtos.java:31889)
at org.apache.hadoop.hbase.client.ScannerCallable.close(ScannerCallable.java:327)
... 20 more
這個(gè)時(shí)候,我們可以采用異步創(chuàng)建索引滓窍,方式如下:
CREATE INDEX async_index ON my_schema.my_table (v) ASYNC
通過create index的時(shí)候指定 ASYNC
關(guān)鍵字來指定異步創(chuàng)建索引卖词。執(zhí)行這個(gè)命令之后并不會(huì)引起索引表與源表的直接同步。這個(gè)時(shí)候查詢并不會(huì)使用這個(gè)索引表吏夯。那么索引數(shù)據(jù)的導(dǎo)入還需要采用phoenix提供的索引同步工具類 IndexTool
此蜈, 這是一個(gè)mapreduce工具類,使用方式如下:
${HBASE_HOME}/bin/hbase org.apache.phoenix.mapreduce.index.IndexTool
--schema MY_SCHEMA --data-table MY_TABLE --index-table ASYNC_IDX
--output-path ASYNC_IDX_HFILES
當(dāng)mapreduce任務(wù)執(zhí)行結(jié)束噪生,這個(gè)索引表才會(huì)變成可用裆赵。
7. 參考
(1) 可變索引與不可變索引
(3) Local Indexing
(4) 配置二級索引及測試
Examples
為已有的Phoenix表創(chuàng)建secondary index
首先,在Phoenix中創(chuàng)建一個(gè)salted table跺嗽。
create table EXAMPLE (my_pk varchar(50) not null, M.C0 varchar(50), M.C1 varchar(50), M.C2 varchar(50), M.C3 varchar(50), M.C4 varchar(50), M.C5 varchar(50), M.C6 varchar(50), M.C7 varchar(50),M.C8 varchar(50), M.C9 varchar(50) constraint pk primary key (my_pk)) salt_buckets=10;
這里創(chuàng)建的表名為EXAMPLE战授,有10個(gè)列(列名為{M.C0, M.C1, ···, M.C9})。
然后桨嫁,再將一個(gè)CSV文件中的數(shù)據(jù)通過Bulkload的方式導(dǎo)入到該表中植兰。
該CSV文件中有1000萬條記錄,在HDFS中的路徑為/user/tao/xt-data/data-10m.csv
瞧甩,則導(dǎo)入的命令為
HADOOP_CLASSPATH=$(hbase classpath) hadoop jar phoenix-4.3.1-client.jar org.apache.phoenix.mapreduce.CsvBulkLoadTool -libjars 3rdlibs/commons-csv-1.0.jar,3rdlibs/joda-time-2.7.jar --table EXAMPLE --input /user/tao/xt-data/data-10m.csv
由于導(dǎo)入的過程用到了一些其他的類钉跷,所以需要通過-libjars 3rdlibs/commons-csv-1.0.jar,3rdlibs/joda-time-2.7.jar
來將相關(guān)的Jar包傳給這個(gè)MapReduce任務(wù)。另外肚逸,Bulk CSV Data Loading中提到的方法爷辙,要求對于Phoenix 4.0+的版本彬坏,指定 HADOOP_CLASSPATH=$(hbase mapredcp)
,但是通過實(shí)踐膝晾,發(fā)現(xiàn)這樣不行栓始,應(yīng)該用HADOOP_CLASSPATH=$(hbase classpath)
。
可以看到幻赚,對名為M.C0的列進(jìn)行按值查詢需要7秒多。
現(xiàn)在臊旭,為表EXAMPLE的列M.C0創(chuàng)建Index落恼,如下:
create index my_index on example (m.c0);
此時(shí),查看HBase离熏,會(huì)發(fā)現(xiàn)其中多了一個(gè)名為MY_INDEX
的表佳谦。而從Phoenix中則看不到該表。
在為EXAMPLE創(chuàng)建了index之后滋戳,我們再來進(jìn)行查詢钻蔑。
這次,查詢時(shí)間從7秒多降到了0.097秒奸鸯。
確保Query使用Index
By default, a global index will not be used unless all of the columns referenced in the query are contained in the index.
在上例中咪笑,由于我們只對M.C0
創(chuàng)建了索引,所以如果查詢項(xiàng)中包含其他列的話(主鍵MY_PK
除外)娄涩,是不會(huì)使用index的窗怒。此外,如果查詢項(xiàng)不包含其他列蓄拣,但是條件查詢語句中包含了其他列(主鍵MY_PK
除外)兜粘,也會(huì)引發(fā)全表掃瞄。如下:
要讓一個(gè)查詢使用index弯蚜,有三種方式:
1. 創(chuàng)建 convered index孔轴;
2. 在查詢中提示其使用index;
3. 創(chuàng)建 local index
#方式1 - Covered Index
What is a Covered Index?
Using Covering Index to Improve Query Performance
如果在某次查詢中碎捺,查詢項(xiàng)或者查詢條件中包含除被索引列之外的列(主鍵MY_PK
除外)路鹰。默認(rèn)情況下,該查詢會(huì)觸發(fā)full table scan收厨,但是使用covered index則可以避免全表掃描晋柱。
例如,我們按照EXAMPLE
的方式創(chuàng)建了另一張表EXAMPLE_2
诵叁,表中的數(shù)據(jù)和表的schema與EXAMPLE
都是相同的雁竞,不同之處在于EXAMPLE_2
對其M.C1
這一列創(chuàng)建了covered index。
create index my_index_2 on example_2 (m.c0) include (m.c1);
現(xiàn)在,如果查詢項(xiàng)中不包含除M.C0
和M.C1
之外的列碑诉,而且查詢條件不包含除M.C0
之外的列彪腔,則可以確保該查詢使用Index,如下:
#方式2 - Hint
在select
和column_name
之間加上/*+ Index(<表名> <index名>)*/
进栽,如下:
This will cause each data row to be retrieved when the index is traversed to find the missing M.C1 column value. This hint should only be used if you know that the index has good selective (i.e. a small number of table rows have a value of ‘c0_00000000’ in this example), as otherwise you’ll get better performance by the default behavior of doing a full table scan
#方式3 - Local Index
Unlike global indexes, local indexes will use an index even when all columns referenced in the query are not contained in the index. This is done by default for local indexes because we know that the table and index data coreside on the same region server thus ensuring the lookup is local.
對于HBase 0.98.6的版本德挣,似乎不支持創(chuàng)建local index,如下:
其他方面
Functional Index
Another useful feature that was introduced in the 4.3 release is functional indexes. Functional indexes allow you to create an index not just on columns, but on an arbitrary expressions. Then when a query uses that expression, the index may be used to retrieve the results instead of the data table. For example, you could create an index on UPPER(FIRST_NAME||' '||LAST_NAME)
to allow you to do case insensitive searches on the combined first name and last name of a person.
Phoenix supports two types of indexing techniques: global and local indexing. Each are useful in different scenarios and have their own failure profiles and performance characteristics.
下面為表EXAMPLE
創(chuàng)建一個(gè)Functional Index快毛,如下:
create index index_upper_c2 on example (upper(m.c2)) include m.c2
這里格嗅,我們實(shí)際上為表
EXAMPLE
又創(chuàng)建了一個(gè)名為INDEX_UPPER_C2
的Index。也就是說唠帝,可以為同一張表創(chuàng)建多個(gè)Index屯掖。
Index 排序
create index my_index on example (M.C1 desc, M.C0) include (M.C2);
刪除Index
drop index `index-name` on `table-name`
索引表屬性
create table
和create index
都可以將一些屬性傳遞給對應(yīng)的HBase表,例如:
CREATE INDEX my_index ON my_table (v2 DESC, v1) INCLUDE (v3)
SALT_BUCKETS=10, DATA_BLOCK_ENCODING='NONE';
對于global indexes襟衰,如果primary table是salted table懂扼,則index會(huì)自動(dòng)地成為salted index。對于local indexes右蒲,則不允許指定salt_buckets
。
Immutable Indexing
For a table in which the data is only written once and never updated in-place, certain optimizations may be made to reduce the write-time overhead for incremental maintenance. This is common with time-series data such as log or event data, where once a row is written, it will never be updated. To take advantage of these optimizations, declare your table as immutable by adding the IMMUTABLE_ROWS=true
property to your DDL statement:
CREATE TABLE my_table (k VARCHAR PRIMARY KEY, v VARCHAR) IMMUTABLE_ROWS=true;
All indexes on a table declared with IMMUTABLE_ROWS=true
are considered immutable (note that by default, tables are considered mutable). For global immutable indexes, the index is maintained entirely on the client-side with the index table being generated as change to the data table occur. Local immutable indexes, on the other hand, are maintained on the server-side. Note that no safeguards are in-place to enforce that a table declared as immutable doesn’t actually mutate data (as that would negate the performance gain achieved). If that was to occur, the index would no longer be in sync with the table.
如果創(chuàng)建的表是immutable table赶熟,如下:
create table my_tablek(VARCHAR PRIMARY KEY, v VARCHAR) IMMUTABLE_ROWS=true
那么瑰妄,為該表創(chuàng)建的所有index都是immutable indexes。
可以將一個(gè)已有的immutable table轉(zhuǎn)變?yōu)閙utable table映砖,可以通過如下命令:
alter table my_table set IMMUTABLE_ROWS=false
問題
#1 能不能為多個(gè)column建立index(在同一個(gè)index table中)间坐?
例如,如下命令似乎是可以對兩個(gè)column創(chuàng)建index:
create index my_idx on example(m.c0, m.c1)
但是邑退,似乎只有列m.c0
的真正具有索引竹宋,列m.c1
似乎沒有索引:
答案是:可以為多個(gè)column創(chuàng)建索引,但是在查詢時(shí)要按照索引列的順序來查詢地技。例如蜈七,為M.C0
、M.C1
和M.C2
建立索引:
create index idx on example (m.c0, m.c1, m.c2)
在查詢時(shí)莫矗,可以同時(shí)根據(jù)將這3列作為條件飒硅,且順序不限。但是作谚,第一列必須是M.C0
三娩。
這是因?yàn)椋?strong>當(dāng)為多列建立索引時(shí),rowkey實(shí)際上是這些column的組合妹懒,并且是按照它們的先后順序的組合雀监。
如果查詢時(shí)第一列不是M.C0
,那么就要進(jìn)行full scan眨唬,速度會(huì)很慢会前。而如果查詢的第一列是M.C0
好乐,就可以直接將滿足關(guān)于M.C0
的數(shù)據(jù)記錄找出來。即使后面還有沒有被索引的列回官,也可以很快得到結(jié)果曹宴,因?yàn)闈M足關(guān)于M.C0
的結(jié)果集已經(jīng)不大了(如果是這種情況的話),對其再進(jìn)行一次查詢不會(huì)是full scan歉提。
#2 Bulkload的數(shù)據(jù)的index能不能自動(dòng)同步笛坦?
維護(hù)Index的原理:當(dāng)對data table執(zhí)行寫入操作時(shí),delete
苔巨、upsert values
和upsert select
會(huì)被捕獲版扩,并據(jù)此來更新data table所對應(yīng)的index。
We intercept the data table updates on write (DELETE, UPSERT VALUES and UPSERT SELECT), build the index update and then sent any necessary updates to all interested index tables
當(dāng)以bulkload的方式來將數(shù)據(jù)導(dǎo)入到data table時(shí)侄泽,會(huì)繞開HBase的常規(guī)寫入路徑(client –> buffer –> memstore –> HLog –> StoreFile –> HFile)礁芦,直接生成最終的HFiles。對于bulkload悼尾,對data table的更新能不能被捕獲柿扣,進(jìn)而自動(dòng)維護(hù)相應(yīng)index呢?我們來驗(yàn)證闺魏。
首先建立一個(gè)空的data table:
create table EXAMPLE (PK varchar primary key, M.C0 varchar, M.C1 varchar, M.C2 varchar, M.C3 varchar, M.C4 varchar, M.C5 varchar, M.C6 varchar, M.C7 varchar, M.C8 varchar, M.C9 varchar) salt_buckets = 20
再為其創(chuàng)建2個(gè)index:
create index IDX_C0 on EXAMPLE(M.C0);
create index IDX_C1 on EXAMPLE(M.C1);
現(xiàn)在用MapReduce將一個(gè)包含了1億行記錄的CSV文件bulkload到數(shù)據(jù)表EXAMPLE
中去:
sudo -u hbase HADOOP_CLASSPATH=$(hbase classpath) hadoop jar phoenix-4.3.1-client.jar org.apache.phoenix.mapreduce.CsvBulkLoadTool -libjars 3rdlibs/commons-csv-1.0.jar,3rdlibs/joda-time-2.7.jar --table EXAMPLE --input /user/tao/data/data-100m.csv
從輸出來看未状,一共啟動(dòng)了3個(gè)MR任務(wù),分別針對數(shù)據(jù)表EXAMPLE
析桥、索引表IDX_C0
和索引表IDX_C1
司草,如下:
現(xiàn)在,再看看查詢時(shí)間:
所以泡仗,一旦index創(chuàng)建之后埋虹,不論是否使用bulkload來更新data table,都會(huì)保證index的自動(dòng)更新娩怎。