Tablestore入门手册–表(Table)管理

表管理接口概述

API 描述
createTable 创建表
deleteTable 删除表
listTable 列出实例下的所有表
updateTable 更新表(在表被创建之后,动态的更改表的配置或预留吞吐量)
describeTable 获取表的详细信息

上述API操作是Tablestore最基础的API。官方提供了Java、Go、Node.js、Python、PHP、C#、C++语言的SDK。

createTable

创建表可以在控制台进行。在使用API进行创建的时候,需要指定表的配置信息和预留吞吐量等信息。

说明

  • 表格创建好后服务端有初始化时间,需要等待几秒钟才能对表进行读写,否则会出现异常

表限制

  • 单个实例最多可以创建 64 张数据表
  • 表名长度1-255 Bytes,需由[a-z, A-Z, 0-9]和下划线(_)组成。首字符必须是字母或下划线(_)
  • 其它限制见: https://help.aliyun.com/document_detail/91524.html?spm=a2c4g.11186623.6.568.357f228382mAWN

参数说明

创建表的参数主要包括如下几部分:

  • TableMeta: 表的结构信息,包含表的名称以及表的主键定义
  • TableOptions: 表的配置选项,用于配置TTL、MaxVersions等
  • ReservedThroughput:表的预留读写吞吐量设置

表结构 TableMeta

参数 定义 说明
TableName 表名
List 表的主键定义 1. 注意: 最多可设置 4 个主键,主键的配置及顺序一旦设置便不可修改。
2. 表格存储可包含多个主键列。主键列是有顺序的,与用户添加的顺序相同,例如, PRIMARY KEY (A, B, C) 与 PRIMARY KEY (A, C, B) 是不同的两个主键结构。表格存储会按照主键的大小为行排序,具体参见表格存储数据模型和查询操作。
3. 第一列主键作为分片键。分片键相同的数据会存放在同一个分片内,所以相同分片键下最好不要超过 10 G 以上数据,否则会导致单分片过大,无法分裂。另外,数据的读/写访问最好在不同的分片键上均匀分布,有利于负载均衡。
4. 属性列不需要定义。表格存储每行的数据列都可以不同,属性列的列名在写入时指定。

表配置 TableOptions

Tablestore入门手册--表(Table)管理

预留吞吐量 ReservedThroughtput

  • ReservedThroughtput表的预留读/写吞吐量配置。

    • 设置 ReservedThroughtput 后,表格存储按照您预留读/写吞吐量进行计费。
    • 当 ReservedThroughtput 大于 0 时,表格存储会按照预留量和持续时间进行计费,超出预留的部分进行按量计费。更多信息参见计费,以免产生未期望的费用。
    • 默认值为 0,即完全按量计费。
    • 容量型实例的预留读/写吞吐量只能设置为 0,不允许预留。

Java代码示例

// 创建普通表(不使用索引等功能)
public void createTable() {
TableMeta tableMeta = new TableMeta(TABLE_NAME);
// 为主表添加主键列。
tableMeta.addPrimaryKeyColumn(new PrimaryKeySchema("pk_1", PrimaryKeyType.STRING));
tableMeta.addPrimaryKeyColumn(new PrimaryKeySchema("pk_2", PrimaryKeyType.INTEGER));
tableMeta.addPrimaryKeyColumn(new PrimaryKeySchema("pk_3", PrimaryKeyType.BINARY));
// 设置该主键为自增列
tableMeta.addPrimaryKeyColumn(new PrimaryKeySchema("pk_4", PrimaryKeyType.INTEGER, PrimaryKeyOption.AUTO_INCREMENT));
// 数据的过期时间,单位秒, -1代表永不过期,例如设置过期时间为一年, 即为 365 * 24 * 3600。
int timeToLive = -1;
// 保存的最大版本数,设置为3即代表每列上最多保存3个最新的版本。(如果使用索引,maxVersions只能等于1)
int maxVersions = 3;
TableOptions tableOptions = new TableOptions(timeToLive, maxVersions);
CreateTableRequest request = new CreateTableRequest(tableMeta, tableOptions);
// 设置读写预留值,容量型实例只能设置为0,高性能实例可以设置为非零值。
request.setReservedThroughput(new ReservedThroughput(new CapacityUnit(0, 0)));
client.createTable(request);
}

表设计相关技巧

分区键选择

表格存储Tablestore第一个主键是分区键,决定了数据分布式存储的位置。分区键的选择决定了数据是否均匀散列在不同的后端服务器上。散列是分布式数据库中常见的问题之一,数据的散列能够避免热点问题。因此在选择主键的时候,首先要选好分区键。其中比较常用的两种方式如下:

  1. 分区键可以选择业务上比较分散的Key放到第一列,如userID、DeviceId等。如果每个用户数据分布严重不均匀,则需要另外选择其它字段。
  2. 如果分区键不好设计,可以对想要当做分区键的值拼接MD5,这样能够保证数据散列。

更多的设计技巧见两篇表设计实践。 表设计的最佳实践 、表设计实践

数据生命周期TTL设计

数据生命周期(Time To Live,简称 TTL)是数据表的一个特性,即数据的存活时间,单位为秒。表格存储会在后台对超过存活时间的数据进行清理,以减少用户的数据存储空间,降低存储成本。

TTL特性能够实现数据过期自动删除,因此在很多场景能够用到,这些场景中随着时间的流逝数据的价值会降低,特别适合TTL。

  1. 舆情监控。如果用户只关心最近3个月的信息,因此超过3个月的数据可以自动删除。
  2. 物流轨迹。物流轨迹的记录信息在用户收到货物后就失去了其大部分的价值,因此可以设置1个月的TTL,来节省大量的存储费用。
  3. 系统日志。系统的运行日志大多数只有在系统出问题时候才会查看,而随着时间流逝,很早之前的系统日志也就失去了存在的价值,而这部分数据存储在数据库中会占用存储成本,设置合适的TTL能够自动将之前的数据删除。

TTL暂时仅支持主表,索引等还暂时不支持TTL特性。因此如果使用了二级索引或者多元索引,需要主表的TTL=-1,即永久不会失效,对于已经创建好表的用户,可以通过 UpdateTable 接口动态更改主表的 TTL。

Java 示例代码

public void updateTTL() {
UpdateTableRequest request = new UpdateTableRequest(TABLE_NAME);
int ttl = -1;
request.setTableOptionsForUpdate(new TableOptions(ttl));
client.updateTable(request);
}

主键自增

主键列自增功能是指若用户指定某一列主键为自增列,在其写入数据时,表格存储会自动为用户在这一列产生一个新的值,且这个值为同一个分区键下该列的最大值。

主键自增列的功能特性主要有生成数字ID且ID严格递增保证顺序,这个特性决定了许多场景能够使用。

  • 电商网站的商品 ID、大型网站的用户 ID等,这些场景中数值越大,表示该商品、用户越新。
  • 如论坛帖子的 ID、聊天工具的消息 ID等消息保序的场景,这些场景需要严格保证消息递增,不然用户读取到的顺序就会乱序。举个例子,假如用户发朋友圈场景,用户在1点5分11.1秒时刻发送了一个朋友圈记录a,另一个用户在1点5分11.2秒发送了一个朋友圈记录b,则需要严格保证记录b的id比记录a的id大。主键自增在聊天系统IM中的应用见 Table Store主键列自增功能在IM系统中的应用

主键自增的一些限制:

  • 表格存储支持多个主键,第一个主键为分区键,分区键不允许设置为自增列,其它任一主键都可以设置为自增列。
  • 每张表最多只允许设置一个主键为自增列。
  • 属性列不能设置为自增列。
  • 仅支持在创建表的时候指定自增列,对于已存在的表不支持创建自增列。

Java示例代码

// 主键自增 创建主表
public void createTableWithPKAutoIncrement() {
TableMeta tableMeta = new TableMeta(TABLE_NAME + "AUTO_INCREMENT");
tableMeta.addPrimaryKeyColumn(new PrimaryKeySchema("pk_1", PrimaryKeyType.STRING));
// 设置该主键为自增列
tableMeta.addPrimaryKeyColumn(new PrimaryKeySchema("pk_autoI_increment", PrimaryKeyType.INTEGER, PrimaryKeyOption.AUTO_INCREMENT));
tableMeta.addPrimaryKeyColumn(new PrimaryKeySchema("pk_3", PrimaryKeyType.INTEGER));
TableOptions tableOptions = new TableOptions(-1, 1);
CreateTableRequest request = new CreateTableRequest(tableMeta, tableOptions);
client.createTable(request);
}
// 自增列的数据插入
public void putRow(){
PrimaryKey primaryKey = PrimaryKeyBuilder.createPrimaryKeyBuilder()
.addPrimaryKeyColumn("pk_1", PrimaryKeyValue.fromString("test1"))
.addPrimaryKeyColumn("pk_auto_increment", PrimaryKeyValue.AUTO_INCREMENT)
.addPrimaryKeyColumn("pk_3", PrimaryKeyValue.fromLong(100))
.build();
RowPutChange rowPutChange = new RowPutChange(TABLE_NAME , primaryKey);
rowPutChange.addColumn("attr_1", ColumnValue.fromLong(100));
PutRowRequest request = new PutRowRequest(rowPutChange);
client.putRow(request);
}

deleteTable

删除表,只需要指定表名字即可。

说明

  • 如果表中创建了多元索引,需要先删除多元索引才可以删除表。

Java代码示例


public void deleteTable() {
DeleteTableRequest request = new DeleteTableRequest(TABLE_NAME);
client.deleteTable(request);
}

listTable

该API能够列出当前实例下已创建的所有表的表名。

Java代码示例


public void listTable() {
ListTableResponse response = client.listTable();
System.out.println("表的列表如下:");
for (String tableName : response.getTableNames()) {
System.out.println(tableName);
}
}    

updateTable

该API能够动态的更改表的配置或预留吞吐量。可以只修改配置或只修改预留吞吐量,也可以一起修改。

说明

  • 该API调用频率有限制,为每 2分钟1次。

Java代码示例


public void updateTable() {
UpdateTableRequest request = new UpdateTableRequest(TABLE_NAME);
// 修改预留吞吐
request.setReservedThroughputForUpdate(new ReservedThroughput(new CapacityUnit(0, 0)));
// 修改表的最大保留版本、TTL等
request.setTableOptionsForUpdate(new TableOptions(-1, 1));
client.updateTable(request);
}

describeTable

该API可以获得表的结构信息(TableMeta)、配置信息(TableOptions)和预留读/写吞吐量的情(ReservedThroughputDetails,包括调整时间)、表分区信息等, 如图所示。

Tablestore入门手册--表(Table)管理

Java代码示例

public void describeTable() {
DescribeTableRequest request = new DescribeTableRequest(TABLE_NAME);
DescribeTableResponse response = client.describeTable(request);
TableMeta tableMeta = response.getTableMeta();
System.out.println("表的名称:" + tableMeta.getTableName());
System.out.println("表的主键:");
for (PrimaryKeySchema schema : tableMeta.getPrimaryKeyList()) {
System.out.println("t主键名字:" + schema.getName() + "t主键类型:" + schema.getType()
+ "t自增列:" + (schema.getOption() == null ? "false" : schema.getOption().equals(PrimaryKeyOption.AUTO_INCREMENT)));
}
System.out.println("预定义列信息:");
for (DefinedColumnSchema schema : tableMeta.getDefinedColumnsList()) {
System.out.println("t主键名字:" + schema.getName() + "t主键类型:" + schema.getType());
}
System.out.println("二级索引信息:");
for (IndexMeta meta : response.getIndexMeta()) {
System.out.println("t索引名字:" + meta.getIndexName());
System.out.println("tt索引类型:" + meta.getIndexType());
System.out.println("tt索引主键:" + meta.getPrimaryKeyList());
System.out.println("tt索引预定义列:" + meta.getDefinedColumnsList());
}
TableOptions tableOptions = response.getTableOptions();
System.out.println("TableOptions:");
System.out.println("t表的TTL:" + tableOptions.getTimeToLive());
System.out.println("t表的MaxVersions:" + tableOptions.getMaxVersions());
ReservedThroughputDetails rtd = response.getReservedThroughputDetails();
System.out.println("预留吞吐量:");
System.out.println("t读:" + rtd.getCapacityUnit().getReadCapacityUnit());
System.out.println("t写:" + rtd.getCapacityUnit().getWriteCapacityUnit());
System.out.println("t最近上调时间: " + new Date(rtd.getLastIncreaseTime() * 1000));
System.out.println("t最近下调时间: " + new Date(rtd.getLastDecreaseTime() * 1000));
List<PrimaryKey> shardSplits = response.getShardSplits();
System.out.println("表分区信息:");
for (PrimaryKey primaryKey : shardSplits) {
System.out.println("t分裂点信息: " + primaryKey.getPrimaryKeyColumnsMap());
}
}

专家服务

如有疑问或者需要更好的在线支持,欢迎加入钉钉群:“表格存储公开交流群”(群号:23307953)。群内提供免费的在线专家服务,欢迎扫码加入。

Tablestore入门手册--表(Table)管理