名詞定義
- 索引表:對主表某些列資料的索引,隻能讀不能寫。
- 預定義列:表格存儲為Schema-free模型,原則上一行資料可以寫入任意列,無需在schema中指定。但是也可以在建表時預先定義一些列,以及其類型。
- 單列索引:隻為某一個列建立索引。
- 組合索引:多個列組合排序,組合索引中包含組合索引列1,列2。
- 索引表屬性列:被映射到索引表非PK列中的主表預定義列。
- 索引列補齊:自動将沒有出現在索列中的主表PK列補充到索引表PK中。
使用場景介紹
全局二級索引是表格存儲提供的一個新特性。當使用者建立一張表時,其所有PK列構成了該表的“一級索引”:即給定完整PK,可以迅速的查找到該PK所在行的資料。但是越來越多的業務場景中,需要對表的屬性列,或者非首列PK進行條件上的查詢,由于沒有足夠的索引資訊,隻能通過進行全表的掃描,配合條件過濾,來得到最終結果,特别是全表資料較多,但最終結果很少時,全表掃描将浪費極大的資源。TableStore提供的二級索引功能(與DynamoDB及HBase的Phoenix方案類似),支援在指定列上建立索引,生成的索引表中資料按使用者指定的索引列進行排序,主表的每一筆寫入都将自動異步同步到索引表。使用者隻向主表中寫入資料,根據索引表進行查詢,在許多場景下,将極大的提高查詢的效率。
以我們常見的電話話單查詢為例,有主表如下:
CellNumber | StartTime(Unix時間戳) | CalledNumber | Duration | BaseStationNumber |
---|---|---|---|---|
123456 | 1532574644 | 654321 | 60 | 1 |
234567 | 1532574714 | 765432 | 10 | |
1532574734 | 20 | 3 | ||
345678 | 1532574795 | 5 | 2 | |
1532574861 | 100 | |||
456789 | 1532584054 | 200 |
其中
CellNumber
、
StartTime
分别代表
主叫号碼
與
通話發生時間
,作為表的聯合主鍵,
CalleNumber
Duration
BaseStationNumber
三列為表的預定義列,分别代表
被叫号碼
通話時長
基站号碼
,每次使用者通話結束後,都會将此次通話的資訊記錄到該表中。可以分别在
被叫号碼
,
基站号碼
列上建立二級索引,來滿足不同角度的查詢需求(具體建立索引的示例代碼見本文附錄)。
現在來看幾種查詢需求:
- 查詢号碼
的所有主叫話單:由于表格存儲為全局有序模型,所有行按主鍵進行排序,并且提供順序掃描(234567
)接口,是以隻需要在調用getRange
接口時,将PK0列的最大及最小值均設定為getRange
,PK1列(通話發生時間)的最小值設定為 ,最大值設定為234567
,對主表進行掃描即可:INT_MAX
private static void getRangeFromMainTable(SyncClient client, long cellNumber) { RangeRowQueryCriteria rangeRowQueryCriteria = new RangeRowQueryCriteria(TABLE_NAME); // 構造主鍵 PrimaryKeyBuilder startPrimaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder(); startPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_1, PrimaryKeyValue.fromLong(cellNumber)); startPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_2, PrimaryKeyValue.fromLong(0)); rangeRowQueryCriteria.setInclusiveStartPrimaryKey(startPrimaryKeyBuilder.build()); // 構造主鍵 PrimaryKeyBuilder endPrimaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder(); endPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_1, PrimaryKeyValue.fromLong(cellNumber)); endPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_2, PrimaryKeyValue.INF_MAX); rangeRowQueryCriteria.setExclusiveEndPrimaryKey(endPrimaryKeyBuilder.build()); rangeRowQueryCriteria.setMaxVersions(1); String strNum = String.format("%d", cellNumber); System.out.println("号碼" + strNum + "的所有主叫話單:"); while (true) { GetRangeResponse getRangeResponse = client.getRange(new GetRangeRequest(rangeRowQueryCriteria)); for (Row row : getRangeResponse.getRows()) { System.out.println(row); } // 若nextStartPrimaryKey不為null, 則繼續讀取. if (getRangeResponse.getNextStartPrimaryKey() != null) { rangeRowQueryCriteria.setInclusiveStartPrimaryKey(getRangeResponse.getNextStartPrimaryKey()); } else { break; } } }
-
的被叫話單:前面提到,表格存儲的模型是對所有行按照主鍵進行排序,由于被叫号碼存在于表的預定義列中,是以無法進行快速查詢。是以可以在123456
被叫号碼
索引表上進行查詢:
索引表
:IndexOnBeCalledNumber
PK0 PK1 PK2 StartTime 這裡注意,系統會自動進行索引列補齊,即把主表的PK添加到索引列後面,共同作為索引表的PK。是以可以看到索引表有三列PK。
由于索引表
是按被叫号碼作為主鍵,是以可以直接掃描索引表得到結果:IndexOnBeCalledNumber
private static void getRangeFromIndexTable(SyncClient client, long cellNumber) { RangeRowQueryCriteria rangeRowQueryCriteria = new RangeRowQueryCriteria(INDEX0_NAME); // 構造主鍵 PrimaryKeyBuilder startPrimaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder(); startPrimaryKeyBuilder.addPrimaryKeyColumn(DEFINED_COL_NAME_1, PrimaryKeyValue.fromLong(cellNumber)); startPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_1, PrimaryKeyValue.INF_MIN); startPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_2, PrimaryKeyValue.INF_MAX); rangeRowQueryCriteria.setInclusiveStartPrimaryKey(startPrimaryKeyBuilder.build()); // 構造主鍵 PrimaryKeyBuilder endPrimaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder(); endPrimaryKeyBuilder.addPrimaryKeyColumn(DEFINED_COL_NAME_1, PrimaryKeyValue.fromLong(cellNumber)); endPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_1, PrimaryKeyValue.INF_MAX); endPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_2, PrimaryKeyValue.INF_MAX); rangeRowQueryCriteria.setExclusiveEndPrimaryKey(endPrimaryKeyBuilder.build()); rangeRowQueryCriteria.setMaxVersions(1); String strNum = String.format("%d", cellNumber); System.out.println("号碼" + strNum + "的所有被叫話單:"); while (true) { GetRangeResponse getRangeResponse = client.getRange(new GetRangeRequest(rangeRowQueryCriteria)); for (Row row : getRangeResponse.getRows()) { System.out.println(row); } // 若nextStartPrimaryKey不為null, 則繼續讀取. if (getRangeResponse.getNextStartPrimaryKey() != null) { rangeRowQueryCriteria.setInclusiveStartPrimaryKey(getRangeResponse.getNextStartPrimaryKey()); } else { break; } } }
- 查詢基站
從時間002
開始的所有話單:與上述示例類似,但是查詢不僅把1532574740
列作為條件,同時把基站号碼
列作為查詢條件,是以我們可以在通話發生時間
和基站号碼
通話發生時間
列上建立組合索引:
資料庫中的記錄将會如下所示:
:IndexOnBaseStation1
然後在IndexOnBaseStation1
private static void getRangeFromIndexTable(SyncClient client, long baseStationNumber, long startTime) { RangeRowQueryCriteria rangeRowQueryCriteria = new RangeRowQueryCriteria(INDEX1_NAME); // 構造主鍵 PrimaryKeyBuilder startPrimaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder(); startPrimaryKeyBuilder.addPrimaryKeyColumn(DEFINED_COL_NAME_3, PrimaryKeyValue.fromLong(baseStationNumber)); startPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_2, PrimaryKeyValue.fromLong(startTime)); startPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_1, PrimaryKeyValue.INF_MIN); rangeRowQueryCriteria.setInclusiveStartPrimaryKey(startPrimaryKeyBuilder.build()); // 構造主鍵 PrimaryKeyBuilder endPrimaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder(); endPrimaryKeyBuilder.addPrimaryKeyColumn(DEFINED_COL_NAME_3, PrimaryKeyValue.fromLong(baseStationNumber)); endPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_2, PrimaryKeyValue.INF_MAX); endPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_1, PrimaryKeyValue.INF_MAX); rangeRowQueryCriteria.setExclusiveEndPrimaryKey(endPrimaryKeyBuilder.build()); rangeRowQueryCriteria.setMaxVersions(1); String strBaseStationNum = String.format("%d", baseStationNumber); String strStartTime = String.format("%d", startTime); System.out.println("基站" + strBaseStationNum + "從時間" + strStartTime + "開始的所有被叫話單:"); while (true) { GetRangeResponse getRangeResponse = client.getRange(new GetRangeRequest(rangeRowQueryCriteria)); for (Row row : getRangeResponse.getRows()) { System.out.println(row); } // 若nextStartPrimaryKey不為null, 則繼續讀取. if (getRangeResponse.getNextStartPrimaryKey() != null) { rangeRowQueryCriteria.setInclusiveStartPrimaryKey(getRangeResponse.getNextStartPrimaryKey()); } else { break; } } }
- 查詢發生在基站
上時間從003
到1532574861
的所有通話記錄的通話時長:此查詢相比前面變的更為複雜,不僅把1532584054
列與基站号碼
列作為了查詢條件,而且隻把通話發生時間
列作為傳回結果,可以仍使用3中的索引,查索引表成功後反查主表得到通話時長:通話時長
private static void getRowFromIndexAndMainTable(SyncClient client, long baseStationNumber, long startTime, long endTime, String colName) { RangeRowQueryCriteria rangeRowQueryCriteria = new RangeRowQueryCriteria(INDEX1_NAME); // 構造主鍵 PrimaryKeyBuilder startPrimaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder(); startPrimaryKeyBuilder.addPrimaryKeyColumn(DEFINED_COL_NAME_3, PrimaryKeyValue.fromLong(baseStationNumber)); startPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_2, PrimaryKeyValue.fromLong(startTime)); startPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_1, PrimaryKeyValue.INF_MIN); rangeRowQueryCriteria.setInclusiveStartPrimaryKey(startPrimaryKeyBuilder.build()); // 構造主鍵 PrimaryKeyBuilder endPrimaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder(); endPrimaryKeyBuilder.addPrimaryKeyColumn(DEFINED_COL_NAME_3, PrimaryKeyValue.fromLong(baseStationNumber)); endPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_2, PrimaryKeyValue.fromLong(endTime)); endPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_1, PrimaryKeyValue.INF_MAX); rangeRowQueryCriteria.setExclusiveEndPrimaryKey(endPrimaryKeyBuilder.build()); rangeRowQueryCriteria.setMaxVersions(1); String strBaseStationNum = String.format("%d", baseStationNumber); String strStartTime = String.format("%d", startTime); String strEndTime = String.format("%d", endTime); System.out.println("基站" + strBaseStationNum + "從時間" + strStartTime + "到" + strEndTime + "的所有話單通話時長:"); while (true) { GetRangeResponse getRangeResponse = client.getRange(new GetRangeRequest(rangeRowQueryCriteria)); for (Row row : getRangeResponse.getRows()) { PrimaryKey curIndexPrimaryKey = row.getPrimaryKey(); PrimaryKeyColumn mainCalledNumber = curIndexPrimaryKey.getPrimaryKeyColumn(PRIMARY_KEY_NAME_1); PrimaryKeyColumn callStartTime = curIndexPrimaryKey.getPrimaryKeyColumn(PRIMARY_KEY_NAME_2); PrimaryKeyBuilder mainTablePKBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder(); mainTablePKBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_1, mainCalledNumber.getValue()); mainTablePKBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_2, callStartTime.getValue()); PrimaryKey mainTablePK = mainTablePKBuilder.build(); // 構造主表PK // 反查主表 SingleRowQueryCriteria criteria = new SingleRowQueryCriteria(TABLE_NAME, mainTablePK); criteria.addColumnsToGet(colName); // 讀取主表的"通話時長"列 // 設定讀取最新版本 criteria.setMaxVersions(1); GetRowResponse getRowResponse = client.getRow(new GetRowRequest(criteria)); Row mainTableRow = getRowResponse.getRow(); System.out.println(mainTableRow); } // 若nextStartPrimaryKey不為null, 則繼續讀取. if (getRangeResponse.getNextStartPrimaryKey() != null) { rangeRowQueryCriteria.setInclusiveStartPrimaryKey(getRangeResponse.getNextStartPrimaryKey()); } else { break; } } }
為了提高查詢效率,可以在`基站号碼`列與`通話發生時間`列上建立組合索引,并把`通話時長`列作為索引表的屬性列:
資料庫中的記錄将會如下所示:
索引表`IndexOnBaseStation2`:
| PK0 | PK1 | PK2 | Defined0 |
| ----------------- | ---------- | ---------- | -------- |
| BaseStationNumber | StartTime | CellNumber | Duration |
| 001 | 1532574644 | 123456 | 60 |
| 001 | 1532574714 | 234567 | 10 |
| 002 | 1532574795 | 345678 | 5 |
| 002 | 1532574861 | 345678 | 100 |
| 003 | 1532574734 | 234567 | 20 |
| 003 | 1532584054 | 456789 | 200 |
然後在`IndexOnBaseStation2`索引表上進行查詢:
private static void getRangeFromIndexTable(SyncClient client,
long baseStationNumber,
long startTime,
long endTime,
String colName) {
RangeRowQueryCriteria rangeRowQueryCriteria = new RangeRowQueryCriteria(INDEX2_NAME);
// 構造主鍵
PrimaryKeyBuilder startPrimaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
startPrimaryKeyBuilder.addPrimaryKeyColumn(DEFINED_COL_NAME_3, PrimaryKeyValue.fromLong(baseStationNumber));
startPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_2, PrimaryKeyValue.fromLong(startTime));
startPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_1, PrimaryKeyValue.INF_MIN);
rangeRowQueryCriteria.setInclusiveStartPrimaryKey(startPrimaryKeyBuilder.build());
// 構造主鍵
PrimaryKeyBuilder endPrimaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
endPrimaryKeyBuilder.addPrimaryKeyColumn(DEFINED_COL_NAME_3, PrimaryKeyValue.fromLong(baseStationNumber));
endPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_2, PrimaryKeyValue.fromLong(endTime));
endPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_1, PrimaryKeyValue.INF_MAX);
rangeRowQueryCriteria.setExclusiveEndPrimaryKey(endPrimaryKeyBuilder.build());
// 設定讀取列
rangeRowQueryCriteria.addColumnsToGet(colName);
rangeRowQueryCriteria.setMaxVersions(1);
String strBaseStationNum = String.format("%d", baseStationNumber);
String strStartTime = String.format("%d", startTime);
String strEndTime = String.format("%d", endTime);
System.out.println("基站" + strBaseStationNum + "從時間" + strStartTime + "到" + strEndTime + "的所有話單通話時長:");
while (true) {
GetRangeResponse getRangeResponse = client.getRange(new GetRangeRequest(rangeRowQueryCriteria));
for (Row row : getRangeResponse.getRows()) {
System.out.println(row);
}
// 若nextStartPrimaryKey不為null, 則繼續讀取.
if (getRangeResponse.getNextStartPrimaryKey() != null) {
rangeRowQueryCriteria.setInclusiveStartPrimaryKey(getRangeResponse.getNextStartPrimaryKey());
} else {
break;
}
}
}
```
可見,如果不把`通話時長`列作為索引表的屬性列,在每次查詢時,都需先從索引表中解出主表的PK,然後對主表進行随機讀。當然,把`通話時長`列作為索引表的屬性列後,該列被同時存儲在了主表及索引表中,增加了總的存儲空間占用,這其實是性能與成本的一個平衡。
-
003
1532574861
的所有通話記錄的總通話時長,平均通話時長,最大通話時長,最小通話時長:相對于上一條查詢,這裡不要求傳回每一條通話記錄的時長,隻要求傳回所有通話時長的統計資訊,使用者可以使用與上條查詢同的的查詢方式,然後自行對傳回的每條通話時長做計算,得到最終結果,也可以使用SQL-on-OTS,省去用戶端的計算,直接使用SQL語句傳回最終統計結果,SQL-on-OTS的開通使用文檔可見 此處 ,其相容絕大多數MySql文法,可以更友善的進行更複雜的,更貼近使用者業務邏輯的計算。1532584054
功能介紹
- 支援全局二級索引,主表與索引表之間異步同步,正常情況下,同步延遲在毫秒級。
- 支援單列索引及組合索引,支援索引表帶有屬性列(Covered Indexes)。主表可以預先定義若幹列(稱為預定義列),可以對任意預定義列和主表PK列進行索引,可以指定主表的若幹個預定義列作為索引表的屬性列(索引表也可以不包含任何屬性列)。當指定了主表的某些預定義列作為索引表的屬性列時,讀索引表可以直接得到主表中對應預定義列的值,無需反查主表,提高了查詢性能(如上面示例4)。例如:主表有PK0, PK1, PK2三列主鍵,Defined0, Defined1, Defined2三列預定義列:
- 索引列可以是PK2,沒有屬性列。
- 索引列可以是PK2,屬性列可以是Defined0。
- 索引列可以是PK3, PK2,沒有屬性列。
- 索引列可以是PK3, PK2,把Defined0作為屬性列。
- 索引列可以是PK2, PK1, PK0,把Defined0, Defined1, Defined2作為屬性列。
- 索引列可以是Defined0,沒有屬性列。
- 索引列可以是Define0, PK1, 把Defined1作為屬性列。
- 索引列可以是Defined1, Defined0,沒有屬性列。
- 索引列可以是Defined1, Defined0,把Defined2作為屬性列。
- 支援稀疏索引(Sparse Indexes):即如果主表的某個預定義列作為索引表的屬性列,當主表某行中不存在該預定義列時,隻要索引列全部存在,仍會為此行建立索引,但如果部分索引列缺失,則不會為此行建立索引。例如:主表有PK0, PK1, PK2三列主鍵,Defined0, Defined1, Defined2三列預定義列,設定索引表PK為Defined0, Defined1, 索引表屬性列為Defined2
- 當主表某行中,隻包含Defined0, Defined1這兩列,不包含Defined2列時,會為此行建立索引。
- 當主表某行中,隻包含Defined0,Defined2這兩列,不包含Defined1列時,不會為此行建立索引。
- 支援在已經存在的主表上進行建立,删除索引的操作。 後續版本将支援建立的索引表中包含主表中的存量資料。
- 查索引表不會自動反查主表,使用者需要自行反查。後續版本将支援自動根據索引表反查主表的功能。
接口說明(以Java SDK為例)
- 建立主表時同時建立索引表
private static void createTable(SyncClient client) { TableMeta tableMeta = new TableMeta(TABLE_NAME); tableMeta.addPrimaryKeyColumn(new PrimaryKeySchema(PRIMARY_KEY_NAME_1, PrimaryKeyType.STRING)); // 為主表設定PK列 tableMeta.addPrimaryKeyColumn(new PrimaryKeySchema(PRIMARY_KEY_NAME_2, PrimaryKeyType.INTEGER)); // 為主表設定PK列 tableMeta.addDefinedColumn(new DefinedColumnSchema(DEFINED_COL_NAME_1, DefinedColumnType.STRING)); // 為主表設定預定義列 tableMeta.addDefinedColumn(new DefinedColumnSchema(DEFINED_COL_NAME_2, DefinedColumnType.INTEGER)); // 為主表設定預定義列 tableMeta.addDefinedColumn(new DefinedColumnSchema(DEFINED_COL_NAME_3, DefinedColumnType.INTEGER)); // 為主表設定預定義列 int timeToLive = -1; // 資料過期時間設定為永不過期 int maxVersions = 1; // 最大版本數為1(帶索引表的主表隻支援版本數為1) TableOptions tableOptions = new TableOptions(timeToLive, maxVersions); ArrayList<IndexMeta> indexMetas = new ArrayList<IndexMeta>(); IndexMeta indexMeta = new IndexMeta(INDEX_NAME); // 建立索引表Meta indexMeta.addPrimaryKeyColumn(DEFINED_COL_NAME_1); // 指定主表的DEFINED_COL_NAME_1列作為索引表的PK indexMeta.addDefinedColumn(DEFINED_COL_NAME_2); // 指定主表的DEFINED_COL_NAME_2列作為索引表的屬性列 indexMetas.add(indexMeta); // 添加索引表到主表 CreateTableRequest request = new CreateTableRequest(tableMeta, tableOptions, indexMetas); // 建立主表 client.createTable(request); }
- 為已經存在的主表添加索引表
注意:目前在添加索引表時,尚不支援索引表中包含主表中已經存在的資料,即建立索引表中将隻包含主表從建立索引表開始時的增量資料。如果有對存量資料建索引的需求,請釘釘聯系表格存儲技術支援private static void createIndex(SyncClient client) { IndexMeta indexMeta = new IndexMeta(INDEX_NAME); // 建立索引Meta indexMeta.addPrimaryKeyColumn(DEFINED_COL_NAME_2); // 指定DEFINED_COL_NAME_2列為索引表的第一列PK indexMeta.addPrimaryKeyColumn(DEFINED_COL_NAME_1); // 指定DEFINED_COL_NAME_1列為索引表的第二列PK CreateIndexRequest request = new CreateIndexRequest(TABLE_NAME, indexMeta, false); // 将索引表添加到主表上 client.createIndex(request); // 建立索引表 }
- 删除索引表
private static void deleteIndex(SyncClient client) { DeleteIndexRequest request = new DeleteIndexRequest(TABLE_NAME, INDEX_NAME); // 指定主表名稱及索引表名稱 client.deleteIndex(request); // 删除索引表 }
-
讀取索引表中資料
需要傳回的屬性列在索引表中,直接讀取索引表:
需要傳回的屬性列不在索引表中,需要反查主表:private static void scanFromIndex(SyncClient client) { RangeRowQueryCriteria rangeRowQueryCriteria = new RangeRowQueryCriteria(INDEX_NAME); // 設定索引表名 // 設定起始主鍵 PrimaryKeyBuilder startPrimaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder(); startPrimaryKeyBuilder.addPrimaryKeyColumn(DEFINED_COL_NAME_1, PrimaryKeyValue.INF_MIN); // 設定需要讀取的索引列最小值 startPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_1, PrimaryKeyValue.INF_MIN); // 主表PK最小值 startPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_2, PrimaryKeyValue.INF_MIN); // 主表PK最小值 rangeRowQueryCriteria.setInclusiveStartPrimaryKey(startPrimaryKeyBuilder.build()); // 設定結束主鍵 PrimaryKeyBuilder endPrimaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder(); endPrimaryKeyBuilder.addPrimaryKeyColumn(DEFINED_COL_NAME_1, PrimaryKeyValue.INF_MAX); // 設定需要讀取的索引列最大值 endPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_1, PrimaryKeyValue.INF_MAX); // 主表PK最大值 endPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_2, PrimaryKeyValue.INF_MAX); // 主表PK最大值 rangeRowQueryCriteria.setExclusiveEndPrimaryKey(endPrimaryKeyBuilder.build()); rangeRowQueryCriteria.setMaxVersions(1); System.out.println("掃描索引表的結果為:"); while (true) { GetRangeResponse getRangeResponse = client.getRange(new GetRangeRequest(rangeRowQueryCriteria)); for (Row row : getRangeResponse.getRows()) { System.out.println(row); } // 若nextStartPrimaryKey不為null, 則繼續讀取. if (getRangeResponse.getNextStartPrimaryKey() != null) { rangeRowQueryCriteria.setInclusiveStartPrimaryKey(getRangeResponse.getNextStartPrimaryKey()); } else { break; } } }
private static void scanFromIndex(SyncClient client) { RangeRowQueryCriteria rangeRowQueryCriteria = new RangeRowQueryCriteria(INDEX_NAME); // 設定索引表名 // 設定起始主鍵 PrimaryKeyBuilder startPrimaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder(); startPrimaryKeyBuilder.addPrimaryKeyColumn(DEFINED_COL_NAME_1, PrimaryKeyValue.INF_MIN); // 設定需要讀取的索引列最小值 startPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_1, PrimaryKeyValue.INF_MIN); // 主表PK最小值 startPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_2, PrimaryKeyValue.INF_MIN); // 主表PK最小值 rangeRowQueryCriteria.setInclusiveStartPrimaryKey(startPrimaryKeyBuilder.build()); // 設定結束主鍵 PrimaryKeyBuilder endPrimaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder(); endPrimaryKeyBuilder.addPrimaryKeyColumn(DEFINED_COL_NAME_1, PrimaryKeyValue.INF_MAX); // 設定需要讀取的索引列最大值 endPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_1, PrimaryKeyValue.INF_MAX); // 主表PK最大值 endPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_2, PrimaryKeyValue.INF_MAX); // 主表PK最大值 rangeRowQueryCriteria.setExclusiveEndPrimaryKey(endPrimaryKeyBuilder.build()); rangeRowQueryCriteria.setMaxVersions(1); while (true) { GetRangeResponse getRangeResponse = client.getRange(new GetRangeRequest(rangeRowQueryCriteria)); for (Row row : getRangeResponse.getRows()) { PrimaryKey curIndexPrimaryKey = row.getPrimaryKey(); PrimaryKeyColumn pk1 = curIndexPrimaryKey.getPrimaryKeyColumn(PRIMARY_KEY_NAME1); PrimaryKeyColumn pk2 = curIndexPrimaryKey.getPrimaryKeyColumn(PRIMARY_KEY_NAME2); PrimaryKeyBuilder mainTablePKBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder(); mainTablePKBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME1, pk1.getValue()); mainTablePKBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME2, ke2.getValue()); PrimaryKey mainTablePK = mainTablePKBuilder.build(); // 根據索引表PK構造主表PK // 反查主表 SingleRowQueryCriteria criteria = new SingleRowQueryCriteria(TABLE_NAME, mainTablePK); criteria.addColumnsToGet(DEFINED_COL_NAME3); // 讀取主表的DEFINED_COL_NAME3列 // 設定讀取最新版本 criteria.setMaxVersions(1); GetRowResponse getRowResponse = client.getRow(new GetRowRequest(criteria)); Row mainTableRow = getRowResponse.getRow(); System.out.println(row); } // 若nextStartPrimaryKey不為null, 則繼續讀取. if (getRangeResponse.getNextStartPrimaryKey() != null) { rangeRowQueryCriteria.setInclusiveStartPrimaryKey(getRangeResponse.getNextStartPrimaryKey()); } else { break; } } }
注意事項
- 對于每張索引表,系統會自動進行索引列補齊。在對索引表進行掃描時,使用者需要注意填充對應PK列的範圍(一般為負無窮到正無窮)。例如:主表有
PK0
兩列PK,PK1
一列預定義列,如果使用者指定在Defined0
列上建立索引,則系統會将索引表的PK生成為Defined0
,Defined0
PK0
。使用者可以指定在PK1
列及Defined0
列上建立索引,則生成索引表的PK為PK1
Defined0
PK1
。使用者還可以在PK0
PK
PK1
。但是無論如果,使用者在建立索引表時,隻需要指定自己關心的索引列,其它列會由系統自動添加。例如主表有PK0, PK1兩列PK,Defined0作為預定義列:PK0
- 如果在Defined0上建立索引,那麼生成的索引表PK将會是Defined0, PK0, PK1三列。
- 如果在PK1上建立索引,那麼生成的索引表PK将會是PK1, PK0兩列。
- 選擇主表的哪些預定義列作為主表的屬性列,是成本和性能的平衡:将主表的一列預定義列作為索引表的屬性列後,查詢時不用反查主表即可得到該列的值,但是同時增加了相應的存儲成本。反之則需要根據索引表反查主表。使用者可以根據自己的查詢模式以及成本的考慮,作出相應的選擇。
- 不建議把時間相關列作為索引表PK的第一列,這樣可能導緻索引表更新速度變慢。建議将時間列進行哈希,然後在哈希後的列上建立索引,如果有類似需求可以釘釘聯系表格存儲技術支援一同讨論表結構。
- 不建議把區取值範圍非常小,甚至可枚舉的列作為索引表PK的第一列,例如
,這樣将導緻索引表水準擴充能力受限,進而影響索引表寫入性能。性别
限制項
- 同一張主表下,最多建立16張索引表。
- 對于一張索引表,其索引列最多有四列(為主表PK以及主表預定義列的任意組合)。
- 索引列的類型為整型,字元串,二進制,布爾, 與主表PK列的限制相同。
- 類型為字元串以及二進制的索引列,大小限制與主表PK列相同。
- 類型為字元串以及二進制的列,作為索引表的屬性列時,限制與主表相同。
- 暫不支援TTL表建立索引,有需求請釘釘聯系表格存儲技術支援。
- 不支援使用多版本功能的表上建立索引。
- 索引表上不允許使用Stream功能。
附錄
建立主表及索引表
private static final String TABLE_NAME = "CallRecordTable";
private static final String INDEX0_NAME = "IndexOnBeCalledNumber";
private static final String INDEX1_NAME = "IndexOnBaseStation1";
private static final String INDEX2_NAME = "IndexOnBaseStation2";
private static final String PRIMARY_KEY_NAME_1 = "CellNumber";
private static final String PRIMARY_KEY_NAME_2 = "StartTime";
private static final String DEFINED_COL_NAME_1 = "CalledNumber";
private static final String DEFINED_COL_NAME_2 = "Duration";
private static final String DEFINED_COL_NAME_3 = "BaseStationNumber";
private static void createTable(SyncClient client) {
TableMeta tableMeta = new TableMeta(TABLE_NAME);
tableMeta.addPrimaryKeyColumn(new PrimaryKeySchema(PRIMARY_KEY_NAME_1, PrimaryKeyType.INTEGER));
tableMeta.addPrimaryKeyColumn(new PrimaryKeySchema(PRIMARY_KEY_NAME_2, PrimaryKeyType.INTEGER));
tableMeta.addDefinedColumn(new DefinedColumnSchema(DEFINED_COL_NAME_1, DefinedColumnType.INTEGER));
tableMeta.addDefinedColumn(new DefinedColumnSchema(DEFINED_COL_NAME_2, DefinedColumnType.INTEGER));
tableMeta.addDefinedColumn(new DefinedColumnSchema(DEFINED_COL_NAME_3, DefinedColumnType.INTEGER));
int timeToLive = -1; // 資料的過期時間, 機關秒, -1代表永不過期. 帶索引表的主表資料過期時間必須為-1
int maxVersions = 1; // 儲存的最大版本數, 帶索引表的請表最大版本數必須為1
TableOptions tableOptions = new TableOptions(timeToLive, maxVersions);
ArrayList<IndexMeta> indexMetas = new ArrayList<IndexMeta>();
IndexMeta indexMeta0 = new IndexMeta(INDEX0_NAME);
indexMeta0.addPrimaryKeyColumn(DEFINED_COL_NAME_1);
indexMetas.add(indexMeta0);
IndexMeta indexMeta1 = new IndexMeta(INDEX1_NAME);
indexMeta1.addPrimaryKeyColumn(DEFINED_COL_NAME_3);
indexMeta1.addPrimaryKeyColumn(PRIMARY_KEY_NAME_2);
indexMetas.add(indexMeta1);
IndexMeta indexMeta2 = new IndexMeta(INDEX2_NAME);
indexMeta2.addPrimaryKeyColumn(DEFINED_COL_NAME_3);
indexMeta2.addPrimaryKeyColumn(PRIMARY_KEY_NAME_2);
indexMeta2.addDefinedColumn(DEFINED_COL_NAME_2);
indexMetas.add(indexMeta2);
CreateTableRequest request = new CreateTableRequest(tableMeta, tableOptions, indexMetas);
client.createTable(request);
}
Reference
試用
目前表格存儲的全局二級索引功能已經在張北公共雲叢集預發邀測,想要試用可以直接釘釘聯系 表格存儲技術支援 或者加入釘釘群 111789671