Tablestore入门手册--全局二级索引使用

本文涉及的产品
对象存储 OSS,20GB 3个月
对象存储 OSS,恶意文件检测 1000次 1年
对象存储 OSS,内容安全 1000次 1年
简介: 指定主键的前缀范围,可以实现对主表的范围扫描(GetRange),查询范围的指定必须和主键范围保持一致。如果查询范围无法表示成主键前缀的形式,则可以使用二级索引重新组合字段顺序。相比范围查询(GetRange)加过滤器(filter)的方式,二级索引可以大大减少扫描数据量,提升查询速度。

Tablestore入门手册--全局二级索引使用

概述

全局二级索引和主表有着相同的存储结构,其索引列可以是主表的主键列或预定义列,其属性列为主表的预定义列。写入主表的数据,经过毫秒级延迟异步同步到全局二级索引,即可被查到。
与主表相同,如GetRow,BatchGetRow和BatchGetRow的查询操作均可以作用于二级索引;与主表不同,二级索引不支持用户直接写,只接受来自主表的数据同步。


指定主键的前缀范围,可以实现对主表的范围扫描(GetRange),查询范围的指定必须和主键范围保持一致。如果查询范围无法表示成主键前缀的形式,则可以使用二级索引重新组合字段顺序。相比范围查询(GetRange)加过滤器(filter)的方式,二级索引可以大大减少扫描数据量,提升查询速度。
本文通过一个例子来阐述二级索引加速查询的本质,使用姿势,并配上完整代码——
以电话话单查询为背景,用户的每次通话,都会被记录在如下主表中:

CellNumber、StartTime作为表的联合主键,分别代表主叫号码与通话发生时间。
CalledNumber、Duration、BaseStationNumber三列为表的预定义列,分别代表被叫号码、通话时长、基站号码。
假想两个查询场景:

查询1:查询号码234567的主叫话单

GetRange直接查询主表即可——指定CellNumber最小值和最大值均为234567;指定StartTime最小值为0,最大值为INT_MAX。

查询2:查询号码123456的被叫话单,并返回基站号码

如果直接查询主表,必须扫描全表,再过滤出被叫号码CalledNumber为123456的行,性能差,成本高。此时,您可以创建一张全局二级索引表,具体做法如下——

  • 全局二级索引schema

将被叫号码CalledNumber放在二级索引表的pk列(系统会自动追加主表pk列补全索引表pk列,保证行的唯一性)。查询要求返回基站号码BaseStationNumber,可以将这一列设置为索引表的属性列,否则还要用索引表查询得到的pk反查主表。因此,二级索引的shema如下所示。

其中,CalledNumber是用户指定的索引pk列;CellNumber和StartTime是系统自动补全的索引pk列。

  • 查询全局二级索引

指定主键前缀范围CalledNumber从123456到123456,CellNumber从INT_MIN到INT_MAX,StartTime从INT_MIN到INT_MAX,使用GetRange查询索引表,并指定返回列包含基站号码。

下面来看看创建、查询以及删除全局二级索引的主要代码。

创建

创建分两种方式,效果等价。


方式一:创建主表的同时创建全局二级索引表

//二级索引IndexMeta
IndexMeta indexMeta = new IndexMeta(indexName);
indexMeta.addPrimaryKeyColumn(CALLED_NUMBER);       //将主表的预定义列"called_number"作为二级索引表的pk列
//此时会自动补齐二级索引表的剩余两个pk列: "cell_number", "start_time"
indexMeta.addDefinedColumn(BASE_STATION_NUMBER);    //将主表的预定义列"base_station_number"作为二级索引表的属性列

//创建主表时,同时创建索引表
TableMeta tableMeta = new TableMeta(tableName);

tableMeta.addPrimaryKeyColumn(CELL_NUMBER, PrimaryKeyType.INTEGER);
tableMeta.addPrimaryKeyColumn(START_TIME, PrimaryKeyType.INTEGER);

tableMeta.addDefinedColumn(new DefinedColumnSchema(CALLED_NUMBER, DefinedColumnType.INTEGER));
tableMeta.addDefinedColumn(new DefinedColumnSchema(BASE_STATION_NUMBER, DefinedColumnType.INTEGER));

TableOptions tableOptions = new TableOptions(-1, 1);
CreateTableRequest createTableRequest = new CreateTableRequest(tableMeta, tableOptions, Arrays.asList(indexMeta));
syncClient.createTable(createTableRequest);

方式二:先创建主表;再为已经存在的主表添加二级索引表

  • 创建主表
TableMeta tableMeta = new TableMeta(tableName);

tableMeta.addPrimaryKeyColumn(CELL_NUMBER, PrimaryKeyType.INTEGER);
tableMeta.addPrimaryKeyColumn(START_TIME, PrimaryKeyType.INTEGER);

tableMeta.addDefinedColumn(new DefinedColumnSchema(CALLED_NUMBER, DefinedColumnType.INTEGER));
tableMeta.addDefinedColumn(new DefinedColumnSchema(BASE_STATION_NUMBER, DefinedColumnType.INTEGER));

// Set TTL to -1, never expire; Set maxVersions to 1, as one version is permitted
TableOptions tableOptions = new TableOptions(-1, 1);
CreateTableRequest createTableRequest = new CreateTableRequest(tableMeta, tableOptions);
syncClient.createTable(createTableRequest);
  • 创建二级索引
    includeBaseData可以指定索引的同步方式。
        true表示:创建索引前主表的存量数据,也会被同步到索引表中;
        false则表示:只会同步创建索引后主表的增量数据。
IndexMeta indexMeta = new IndexMeta(indexName);
indexMeta.addPrimaryKeyColumn(CALLED_NUMBER);       //将主表的预定义列"called_number"作为二级索引表的pk列
                                                    //此时会自动补齐二级索引表的剩余两个pk列: "cell_number", "start_time"
indexMeta.addDefinedColumn(BASE_STATION_NUMBER);    //将主表的预定义列"base_station_number"作为二级索引表的属性列

//全局二级索引索引创建请求(includeBaseData为true表示先同步主表全量数据,再同步增量数据; includeBaseData为false表示只同步增量数据)
CreateIndexRequest request = new CreateIndexRequest(tableName, indexMeta, true);

//创建全局二级索引
syncClient.createIndex(request);

查询

查询二级索引使用和查询主表一样的方式。在这个例子中,用户指定二级索引pk列为CalledNumber,系统会自动补齐剩余两列主键列CellNumber和StartTime。创建二级索引时,指定预定义列BaseStationNumber为属性列,因此直接查询索引即可返回“基站号码”信息,无需反查主表。但如果要返回通话时长Duration列,则需要先查询二级索引返回主键列,再用主键列反查主表。
本例中,查询号码123456的被叫话单,并返回基站号码可以这样写:

RangeRowQueryCriteria rangeRowQueryCriteria = new RangeRowQueryCriteria(indexName);

long calledNumber = 123456L;

// 构造主键
PrimaryKeyBuilder startPrimaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
startPrimaryKeyBuilder.addPrimaryKeyColumn(CALLED_NUMBER, PrimaryKeyValue.fromLong(calledNumber));
startPrimaryKeyBuilder.addPrimaryKeyColumn(CELL_NUMBER, PrimaryKeyValue.INF_MIN);
startPrimaryKeyBuilder.addPrimaryKeyColumn(START_TIME, PrimaryKeyValue.INF_MIN);
rangeRowQueryCriteria.setInclusiveStartPrimaryKey(startPrimaryKeyBuilder.build());

// 构造主键
PrimaryKeyBuilder endPrimaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
endPrimaryKeyBuilder.addPrimaryKeyColumn(CALLED_NUMBER, PrimaryKeyValue.fromLong(calledNumber));
endPrimaryKeyBuilder.addPrimaryKeyColumn(CELL_NUMBER, PrimaryKeyValue.INF_MAX);
endPrimaryKeyBuilder.addPrimaryKeyColumn(START_TIME, PrimaryKeyValue.INF_MAX);
rangeRowQueryCriteria.setExclusiveEndPrimaryKey(endPrimaryKeyBuilder.build());

rangeRowQueryCriteria.setMaxVersions(1);
rangeRowQueryCriteria.addColumnsToGet(BASE_STATION_NUMBER); //查询二级索引,返回pk列和属性列"base_station_number"

System.out.println(String.format("号码 %d 的所有被叫话单: ", calledNumber));
while (true) {
    GetRangeResponse getRangeResponse = syncClient.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;
    }
}

列出二级索引

列出一张主表有哪些二级索引。

DescribeTableRequest request = new DescribeTableRequest(tableName);
DescribeTableResponse response = syncClient.describeTable(request);
for (IndexMeta indexMeta : response.getIndexMeta()) {
    System.out.println(indexMeta.getIndexName());
}

查询二级索引meta信息

可以如同查询主表meta一样,查询二级索引的meta信息。

DescribeTableResponse response = syncClient.describeTable(new DescribeTableRequest(indexName));
System.out.println(response.getTableMeta());

删除

删除二级索引。

DeleteIndexRequest request = new DeleteIndexRequest(tableName, indexName);
syncClient.deleteIndex(request);

代码

完整代码在这里:https://github.com/aliyun/tablestore-examples/tree/master/basic/Java/GlobalIndexCRD

总结

二级索引以另一种顺序重组主表的主键列和预定义列,在特定的场景下,您可以避免大范围的扫描主表,极大提升了查询效率。更多细节可以参考官网文档 https://help.aliyun.com/document_detail/91947.html
如有疑问或者需要更好的在线支持,欢迎加入钉钉群:“表格存储公开交流群”(群号:23307953)。群内提供免费的在线专家服务,欢迎扫码加入。
image.png

相关实践学习
阿里云表格存储使用教程
表格存储(Table Store)是构建在阿里云飞天分布式系统之上的分布式NoSQL数据存储服务,根据99.99%的高可用以及11个9的数据可靠性的标准设计。表格存储通过数据分片和负载均衡技术,实现数据规模与访问并发上的无缝扩展,提供海量结构化数据的存储和实时访问。 产品详情:https://www.aliyun.com/product/ots
目录
相关文章
|
存储 SQL 移动开发
Tablestore入门手册-UpdateRow接口详解| 1月14号云栖号夜读
今天的首篇文章,讲述了:表格存储Tablestore入门手册系列主要介绍表格存储的各个功能接口和适用场景,帮助客户了解和使用表格存储Tablestore。本文对表格存储Tablestore的UpdateRow接口进行介绍,包括其参数、功能示例、使用场景等。
2741 0
|
SQL 存储 NoSQL
Tablestore入门手册-条件更新
功能说明 条件更新功能只有在满足条件时才对表中的数据进行更改,当不满足条件时更新失败。 比如有如下场景,初始化数据,当数据字段A为-1时,将A的值更新为指定的内容。比如更新为12;如果不是-1则更新失败。   条件更新支持两个维度。分别是行的存在性检查和列值的条件判断。 第一个维度是行的条件检查,包括如下三种条件: IGNORE:忽略,不做存在
1162 0
|
存储 NoSQL JavaScript
Tablestore入门手册-数据管理-GetRow
GetRow接口概述     GetRow接口用于读取一行数据,是Tablestore最基础的API之一。官方提供了Java、Go、Node.js、Python、PHP、C#、C++ SDK。     本文以Java代码为例,对GetRow接口进行详细说明。 基本使用说明 参数说明 参数名称 是否必填 参数说
981 0
|
存储 NoSQL JavaScript
Tablestore入门手册--表(Table)管理
表管理接口概述 API 描述 createTable 创建表 deleteTable 删除表 listTable 列出实例下的所有表 updateTable 更新表(在表被创建之后,动态的更改表的配置或预留吞吐量)
1831 0
|
存储 NoSQL Java
Tablestore入门手册--数据表meta信息介绍
若有幸这篇文章也许会是您准备上手实践Tablestore的第一篇帮助文档,本文将通过介绍Tablestore的基础模型使大家理解Tablestore的数据表结构。主要讲如何设置数据表的Meta信息,建一张最适合自己业务的表,祝阅有所得,漫步云端。
1347 0
Tablestore入门手册--数据表meta信息介绍