二级索引支持在指定列上建立索引,生成的索引表中的数据按照指定的索引列进行排序,数据表的每一个数据写入都会自动同步到索引表中。您只需向数据表中写入数据,然后根据索引表进行查询,在许多场景下能提高查询的效率。
全局二级索引
在电话话单查询场景下,使用全局二级索引可以满足不同的查询需求。
- CellNumber、StartTime作为数据表的主键,分别代表主叫号码和通话发生时间。
- CalledNumber、Duration和BaseStationNumber三列为数据表的预定义列,分别代表被叫号码、通话时长和基站号码。
CellNumber | StartTime(Unix时间戳) | CalledNumber | Duration | BaseStationNumber |
---|---|---|---|---|
123456 | 1532574644 | 654321 | 60 | 1 |
234567 | 1532574714 | 765432 | 10 | 1 |
234567 | 1532574734 | 123456 | 20 | 3 |
345678 | 1532574795 | 123456 | 5 | 2 |
345678 | 1532574861 | 123456 | 100 | 2 |
456789 | 1532584054 | 345678 | 200 | 3 |
通过在CalledNumber和BaseStationNumber列上创建索引表,可以满足不同的查询需求。创建索引表的示例代码请参见附录。
假设有如下几种查询需求:
- 查询号码234567的所有主叫话单。
表格存储的模型是对所有行按照主键进行排序,并且提供顺序扫描(getRange)接口,所以只需要在调用getRange接口时,将CellNumber列(主叫号码)的最大及最小值均设置为234567,StartTime列(通话发生时间)的最小值设置为0,最大值设置为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上进行查询,由于索引表IndexOnBeCalledNumber是按照被叫号码作为主键,可以直接调用getRange接口扫描索引表得到结果。
索引表IndexOnBeCalledNumber的信息请参见下表。
说明 系统会自动进行索引列补齐。即把数据表的主键添加到索引列后,共同作为索引表的主键,所以索引表中有三列主键。PK0 PK1 PK2 CalledNumber CellNumber StartTime 123456 234567 1532574734 123456 345678 1532574795 123456 345678 1532574861 654321 123456 1532574644 765432 234567 1532574714 345678 456789 1532584054 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_MIN); 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开始的所有话单。
与上述示例类似,但是查询不仅把BaseStationNumber列作为条件,同时把StartTime列作为查询条件,因此可以在BaseStationNumber和StartTime列上建立组合索引,索引表名称为IndexOnBaseStation1,然后在索引表IndexOnBaseStation1上进行查询。
索引表IndexOnBaseStation1的信息请参见下表。
PK0 PK1 PK2 BaseStationNumber StartTime CellNumber 001 1532574644 123456 001 1532574714 234567 002 1532574795 345678 002 1532574861 345678 003 1532574734 234567 003 1532584054 456789 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的所有通话记录的通话时长。
在该查询中不仅把BaseStationNumber列和StartTime列作为查询条件,而且只把Duration列作为返回结果。您可以使用上一个查询中的索引表IndexOnBaseStation1,查询索引表成功后反查数据表获取通话时长。
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; } } }
为了提高查询效率,可以在BaseStationNumber列和StartTime列上建立组合索引,并把Duration列作为索引表的属性列,索引表名称为IndexOnBaseStation2,然后在索引表IndexOnBaseStation2上进行查询。
索引表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 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; } } } ```
由此可见,如果不把Duration列作为索引表的属性列,在每次查询时都需先从索引表中获取数据表的主键,然后对数据表进行随机读。把Duration列作为索引表的属性列后,该列被同时存储在了数据表和索引表中,增加了占用的总存储空间。
- 查询发生在基站003上时间从1532574861到1532584054的所有通话记录的总通话时长、平均通话时长、最大通话时长和最小通话时长。
相对于上一条查询,此处不要求返回每一条通话记录的时长,只要求返回所有通话时长的统计信息。您可以使用与上一条查询相同的查询方式,然后自行对返回的每条通话时长做计算并得到最终结果。也可以使用Data Lake Analytics,无需客户端计算,直接使用SQL语句返回最终统计结果。说明 关于Data Lake Analytics使用的更多信息,请参见OLAP on Tablestore:基于Data Lake Analytics的Serverless SQL大数据分析。其兼容绝大多数MySQL语法,可以进行更复杂、更贴近用户业务逻辑的计算。
本地二级索引
在电话话单查询场景下,使用本地二级索引可以满足不同的查询需求。
- CellNumber、StartTime作为数据表的主键,分别代表主叫号码和通话发生时间。
- CalledNumber、Duration和BaseStationNumber三列为数据表的预定义列,分别代表被叫号码、通话时长和基站号码。
CellNumber | StartTime(Unix时间戳) | CalledNumber | Duration | BaseStationNumber |
---|---|---|---|---|
123456 | 1532574644 | 654321 | 60 | 1 |
123456 | 1532574704 | 236789 | 60 | 1 |
234567 | 1532574714 | 765432 | 10 | 1 |
234567 | 1532574734 | 123456 | 20 | 3 |
345678 | 1532574795 | 123456 | 5 | 2 |
345678 | 1532574861 | 123456 | 100 | 2 |
456789 | 1532584054 | 345678 | 200 | 3 |
456789 | 1532585054 | 123456 | 200 | 3 |
456789 | 1532586054 | 234567 | 200 | 3 |
456789 | 1532587054 | 123456 | 200 | 3 |
您可以在被叫号码列上建立本地二级索引LocalIndexOnBeCalledNumber上进行查询,来满足查询指定的主叫号码和指定的被叫号码之间通话记录的需求。
索引表LocalIndexOnBeCalledNumber的信息请参见下表。
PK0 | Defined0 | PK1 | Defined1 | Defined2 |
---|---|---|---|---|
CellNumber | CalledNumber | StartTime(Unix时间戳) | Duration | BaseStationNumber |
123456 | 236789 | 1532574704 | 60 | 1 |
123456 | 654321 | 1532574644 | 60 | 1 |
234567 | 123456 | 1532574734 | 20 | 3 |
234567 | 765432 | 1532574714 | 10 | 1 |
345678 | 123456 | 1532574795 | 5 | 2 |
345678 | 123456 | 1532574861 | 100 | 2 |
456789 | 123456 | 1532585054 | 200 | 3 |
456789 | 123456 | 1532587054 | 200 | 3 |
456789 | 234567 | 1532586054 | 200 | 3 |
456789 | 345678 | 1532584054 | 200 | 3 |
如果要查询主叫号码456789到被叫号码123456的所有话单,您只需要在调用getRange接口时,将PK0列(主叫号码)的最大值和最小值均设置为456789,Defined0列(被叫号码)的最大值和最小值均设置为123456,PK1(开始时间)的最小值设置为0, 最大值设置为INT_MAX,对数据表进行扫描即可。示例代码如下:
private static void getRangeFromMainTable(SyncClient client, long cellNumber, long calledNumber){
RangeRowQueryCriteria rangeRowQueryCriteria = new RangeRowQueryCriteria(TABLE_NAME);
// 构造起始主键。
PrimaryKeyBuilder startPrimaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
startPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_0, PrimaryKeyValue.fromLong(cellNumber));
startPrimaryKeyBuilder.addPrimaryKeyColumn(DEFINED_COL_NAME_0, PrimaryKeyValue.fromLong(calledNumber));
startPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_1, PrimaryKeyValue.fromLong(0));
rangeRowQueryCriteria.setInclusiveStartPrimaryKey(startPrimaryKeyBuilder.build());
// 构造结束主键。
PrimaryKeyBuilder endPrimaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
endPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_0, PrimaryKeyValue.fromLong(cellNumber));
endPrimaryKeyBuilder.addPrimaryKeyColumn(DEFINED_COL_NAME_0, PrimaryKeyValue.fromLong(calledNumber));
endPrimaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME_1, PrimaryKeyValue.INF_MAX);
rangeRowQueryCriteria.setExclusiveEndPrimaryKey(endPrimaryKeyBuilder.build());
rangeRowQueryCriteria.setMaxVersions(1);
String strNum = String.format("%d", cellNumber);
String strCalledNum = String.format("%d", calledNumber);
System.out.println("号码" + strNum + "和号码" +strCalledNum+ "的所有话单:");
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;
}
}
}