表格存储提供了BatchWriteRow、BatchGetRow、GetRange等多行数据操作的接口。

说明 组成表的基本单位为行,行由主键和属性组成。其中主键是必须的,且每一行的主键列的名称和类型相同;属性不是必须的,且每一行的属性可以不同。更多信息,请参见模型介绍

接口

多行数据操作包括BatchWriteRow、BatchGetRow和GetRange接口,详细说明请参见下表。

接口 说明
BatchGetRow 批量读取一个或多个表中多行数据。
BatchWriteRow 批量插入、批量更新或者批量删除一个或多个表中的多行数据。
GetRange 根据主键范围查询表中数据。

使用

您可以使用如下语言的SDK实现多行数据操作。

批量写(BatchWriteRow)

BatchWriteRow接口用于在一次请求中进行批量的写入操作,也支持一次对多张表进行写入。BatchWriteRow操作由多个PutRow、UpdateRow、DeleteRow子操作组成,构造子操作的过程与使用PutRow接口、UpdateRow接口和DeleteRow接口时相同,也支持设置更新条件。

BatchWriteRow操作的各个子操作独立执行,表格存储会分别返回各个子操作的执行结果。

由于批量写入可能存在部分行失败的情况,失败行的Index及错误信息在返回的BatchWriteRowResponse中,但并不抛出异常。因此调用BatchWriteRow接口时,需要检查返回值,可通过BatchWriteRowResponse的isAllSucceed方法判断是否全部成功;如果不检查返回值,则可能会忽略掉部分操作的失败。

当服务端检查到某些操作出现参数错误时,BatchWriteRow接口可能会抛出参数错误的异常,此时该请求中所有的操作都未执行。

  • 参数

    更多信息,请参见单行数据操作

  • 示例

    一次BatchWriteRow请求,包含2个PutRow操作、1个UpdateRow操作和1个DeleteRow操作。

    private static void batchWriteRow(SyncClient client) {
        BatchWriteRowRequest batchWriteRowRequest = new BatchWriteRowRequest();
    
        //构造rowPutChange1。
        PrimaryKeyBuilder pk1Builder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
        pk1Builder.addPrimaryKeyColumn(PRIMARY_KEY_NAME, PrimaryKeyValue.fromString("pk1"));
        RowPutChange rowPutChange1 = new RowPutChange(TABLE_NAME, pk1Builder.build());
        //添加一些列。
        for (int i = 0; i < 10; i++) {
            rowPutChange1.addColumn(new Column("Col" + i, ColumnValue.fromLong(i)));
        }
        //添加到batch操作中。
        batchWriteRowRequest.addRowChange(rowPutChange1);
    
        //构造rowPutChange2。
        PrimaryKeyBuilder pk2Builder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
        pk2Builder.addPrimaryKeyColumn(PRIMARY_KEY_NAME, PrimaryKeyValue.fromString("pk2"));
        RowPutChange rowPutChange2 = new RowPutChange(TABLE_NAME, pk2Builder.build());
        //添加一些列。
        for (int i = 0; i < 10; i++) {
            rowPutChange2.addColumn(new Column("Col" + i, ColumnValue.fromLong(i)));
        }
        //添加到batch操作中。
        batchWriteRowRequest.addRowChange(rowPutChange2);
    
        //构造rowUpdateChange。
        PrimaryKeyBuilder pk3Builder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
        pk3Builder.addPrimaryKeyColumn(PRIMARY_KEY_NAME, PrimaryKeyValue.fromString("pk3"));
        RowUpdateChange rowUpdateChange = new RowUpdateChange(TABLE_NAME, pk3Builder.build());
        //添加一些列。
        for (int i = 0; i < 10; i++) {
            rowUpdateChange.put(new Column("Col" + i, ColumnValue.fromLong(i)));
        }
        //删除一列。
        rowUpdateChange.deleteColumns("Col10");
        //添加到batch操作中。
        batchWriteRowRequest.addRowChange(rowUpdateChange);
    
        //构造rowDeleteChange。
        PrimaryKeyBuilder pk4Builder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
        pk4Builder.addPrimaryKeyColumn(PRIMARY_KEY_NAME, PrimaryKeyValue.fromString("pk4"));
        RowDeleteChange rowDeleteChange = new RowDeleteChange(TABLE_NAME, pk4Builder.build());
        //添加到batch操作中。
        batchWriteRowRequest.addRowChange(rowDeleteChange);
    
        BatchWriteRowResponse response = client.batchWriteRow(batchWriteRowRequest);
    
        System.out.println("是否全部成功:" + response.isAllSucceed());
        if (!response.isAllSucceed()) {
            for (BatchWriteRowResponse.RowResult rowResult : response.getFailedRows()) {
                System.out.println("失败的行:" + batchWriteRowRequest.getRowChange(rowResult.getTableName(), rowResult.getIndex()).getPrimaryKey());
                System.out.println("失败原因:" + rowResult.getError());
            }
            /**
             * 可以通过createRequestForRetry方法再构造一个请求对失败的行进行重试。此处只给出构造重试请求的部分。
             * 推荐的重试方法是使用SDK的自定义重试策略功能,支持对batch操作的部分行错误进行重试。设置重试策略后,调用接口处无需增加重试代码。
             */
            BatchWriteRowRequest retryRequest = batchWriteRowRequest.createRequestForRetry(response.getFailedRows());
        }
    }           

批量读(BatchGetRow)

BatchGetRow接口用于一次请求读取多行数据,也支持一次对多张表进行读取。BatchGetRow操作由多个GetRow子操作组成。构造子操作的过程与使用GetRow接口时相同。

批量读取的所有行采用相同的参数条件,例如ColumnsToGet=[colA],则要读取的所有行都只读取colA列。

BatchGetRow操作的各个子操作独立执行,表格存储会分别返回各个子操作的执行结果。

由于批量读取可能存在部分行失败的情况,失败行的错误信息在返回的BatchGetRowResponse中,但并不抛出异常。因此调用BatchGetRow接口时,需要检查返回值,可通过BatchGetRowResponse的isAllSucceed方法判断是否所有行都获取成功;通过BatchGetRowResponse的getFailedRows方法获取失败行的信息。

  • 参数

    更多信息,请参见单行数据操作

  • 示例

    读取10行,设置版本条件、要读取的列、过滤器等。

    private static void batchGetRow(SyncClient client) {
        MultiRowQueryCriteria multiRowQueryCriteria = new MultiRowQueryCriteria(TABLE_NAME);
        //加入10个要读取的行。
        for (int i = 0; i < 10; i++) {
            PrimaryKeyBuilder primaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
            primaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME, PrimaryKeyValue.fromString("pk" + i));
            PrimaryKey primaryKey = primaryKeyBuilder.build();
            multiRowQueryCriteria.addRow(primaryKey);
        }
        //添加条件。
        multiRowQueryCriteria.setMaxVersions(1);
        multiRowQueryCriteria.addColumnsToGet("Col0");
        multiRowQueryCriteria.addColumnsToGet("Col1");
        SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter("Col0",
                SingleColumnValueFilter.CompareOperator.EQUAL, ColumnValue.fromLong(0));
        singleColumnValueFilter.setPassIfMissing(false);
        multiRowQueryCriteria.setFilter(singleColumnValueFilter);
    
        BatchGetRowRequest batchGetRowRequest = new BatchGetRowRequest();
        //BatchGetRow支持读取多个表的数据,一个multiRowQueryCriteria对应一个表的查询条件,可以添加多个multiRowQueryCriteria。
        batchGetRowRequest.addMultiRowQueryCriteria(multiRowQueryCriteria);
    
        BatchGetRowResponse batchGetRowResponse = client.batchGetRow(batchGetRowRequest);
    
        System.out.println("是否全部成功:" + batchGetRowResponse.isAllSucceed());
        if (!batchGetRowResponse.isAllSucceed()) {
            for (BatchGetRowResponse.RowResult rowResult : batchGetRowResponse.getFailedRows()) {
                System.out.println("失败的行:" + batchGetRowRequest.getPrimaryKey(rowResult.getTableName(), rowResult.getIndex()));
                System.out.println("失败原因:" + rowResult.getError());
            }
    
            /**
             * 可以通过createRequestForRetry方法再构造一个请求对失败的行进行重试。此处只给出构造重试请求的部分。
             * 推荐的重试方法是使用SDK的自定义重试策略功能,支持对batch操作的部分行错误进行重试。设置重试策略后,调用接口处无需增加重试代码。
             */
                    BatchGetRowRequest retryRequest = batchGetRowRequest.createRequestForRetry(batchGetRowResponse.getFailedRows());
        }
    }
                

范围读(GetRange)

GetRange接口用于根据主键范围读取表中数据,主键范围为左闭右开的区间。

GetRange操作支持按照确定的主键范围进行正序读取和逆序读取,可以设置要读取的行数。如果范围较大,已扫描的行数或者数据量超过一定限制,会停止扫描,并返回已获取的行和下一个主键信息。您可以根据返回的下一个主键信息,继续发起请求,获取范围内剩余的行。

说明 表格存储表中的行都是按照主键排序的,而主键是按照第一主键列、第二主键列等依次顺序组成的,所以不能理解为表格存储会按照某列主键排序,这是常见的误区。

GetRange操作遵循最左匹配原则,读取数据时,依次比较第一主键列到第四主键列。例如数据表的主键包括PK1、PK2、PK3三个主键列,读取数据时,优先比较PK1是否在开始主键与结束主键的范围内,如果PK1在设置的主键范围内,则不会再比较其他的主键,返回在PK1主键范围内的数据;如果PK1不在设置的主键范围内,则继续比较PK2是否在开始主键与结束主键的范围内,以此类推。关于范围查询原理的更多信息,请参见GetRange范围查询详解

GetRange操作可能在如下情况停止执行并返回数据。
  • 扫描的行数据大小之和达到4 MB。
  • 扫描的行数等于5000。
  • 返回的行数等于最大返回行数。
  • 当前剩余的预留读吞吐量已全部使用,余量不足以读取下一条数据。

GetRange操作消耗的读CU为从区间起始点到下一条未读数据的起始点,所有行主键数据大小与实际扫描的所有属性列数据大小之和按4 KB向上取整计算消耗的读CU。例如扫描范围中包含10行,每行主键数据大小与实际扫描的所有属性列数据之和为330 Byte,则消耗的读CU为1(数据总和3.3 KB,除以4 KB向上取整为1)。

  • 参数
    参数 说明
    tableName 数据表名称。
    direction 读取方向。
    • 如果值为正序(FORWARD),则起始主键必须小于结束主键,返回的行按照主键由小到大的顺序进行排列。
    • 如果值为逆序(BACKWARD),则起始主键必须大于结束主键,返回的行按照主键由大到小的顺序进行排列。

    例如同一表中有两个主键A和B,A<B。如正序读取[A, B),则按从A至B的顺序返回主键大于等于A、小于B的行;逆序读取[B, A),则按从B至A的顺序返回大于A、小于等于B的数据。

    inclusiveStartPrimaryKey 本次范围读取的起始主键和结束主键,起始主键和结束主键需要是有效的主键或者是由INF_MIN和INF_MAX类型组成的虚拟点,虚拟点的列数必须与主键相同。

    其中INF_MIN表示无限小,任何类型的值都比它大;INF_MAX表示无限大,任何类型的值都比它小。

    • inclusiveStartPrimaryKey表示起始主键,如果该行存在,则返回结果中一定会包含此行。
    • exclusiveEndPrimaryKey表示结束主键,无论该行是否存在,返回结果中都不会包含此行。

    数据表中的行按主键从小到大排序,读取范围是一个左闭右开的区间,正序读取时,返回的是大于等于起始主键且小于结束主键的所有的行。

    exclusiveEndPrimaryKey
    limit 数据的最大返回行数,此值必须大于 0。

    表格存储按照正序或者逆序返回指定的最大返回行数后即结束该操作的执行,即使该区间内仍有未返回的数据。此时可以通过返回结果中的nextStartPrimaryKey记录本次读取到的位置,用于下一次读取。

    columnsToGet 读取的列集合,列名可以是主键列或属性列。

    如果不设置返回的列名,则返回整行数据。

    说明
    • 查询一行数据时,默认返回此行所有列的数据。如果需要只返回特定列,可以通过设置columnsToGet参数限制。如果将col0和col1加入到columnsToGet中,则只返回col0和col1列的值。
    • 如果某行数据的主键属于读取范围,但是该行数据不包含指定返回的列,那么返回结果中不包含该行数据。
    • 当columnsToGet和filter同时使用时,执行顺序是先获取columnsToGet指定的列,再在返回的列中进行条件过滤。
    maxVersions 最多读取的版本数。
    说明 maxVersions与timeRange必须至少设置一个。
    • 如果仅设置maxVersions,则返回所有版本中从新到旧最多指定数量版本的数据。
    • 如果仅设置timeRange,则返回该范围内所有数据或指定版本数据。
    • 如果同时设置maxVersions和timeRange,则返回版本号范围内从新到旧最多指定数量版本的数据。
    timeRange 读取版本号范围或特定版本号的数据。更多信息,请参见TimeRange
    说明 maxVersions与timeRange必须至少设置一个。
    • 如果仅设置maxVersions,则返回所有版本中从新到旧最多指定数量版本的数据。
    • 如果仅设置timeRange,则返回该范围内所有数据或指定版本数据。
    • 如果同时设置maxVersions和timeRange,则返回版本号范围内从新到旧最多指定数量版本的数据。
    • 如果查询一个范围的数据,则需要设置start和end。start和end分别表示起始时间戳和结束时间戳,范围为前闭后开区间,即[start, end)。
    • 如果查询特定版本号的数据,则需要设置timestamp。timestamp表示特定的时间戳。

    timestamp和[start, end)中只需要设置一个。

    时间戳的单位为毫秒,最小值为0,最大值为Long.MAX_VALUE。

    filter 使用过滤器,在服务端对读取结果再进行一次过滤,只返回符合过滤器中条件的数据行。更多信息,请参见过滤器
    说明 当columnsToGet和filter同时使用时,执行顺序是先获取columnsToGet指定的列,再在返回的列中进行条件过滤。
    nextStartPrimaryKey 根据返回结果中的nextStartPrimaryKey判断数据是否全部读取。
    • 当返回结果中nextStartPrimaryKey不为空时,可以使用此返回值作为下一次GetRange操作的起始点继续读取数据。
    • 当返回结果中nextStartPrimaryKey为空,表示读取范围内的数据全部返回。
  • 示例1
    按照确定范围进行正序读取,判断nextStartPrimaryKey是否为空,读取完范围内的全部数据。
    private static void getRange(SyncClient client, String startPkValue, String endPkValue) {
        RangeRowQueryCriteria rangeRowQueryCriteria = new RangeRowQueryCriteria(TABLE_NAME);
    
        //设置起始主键。
        PrimaryKeyBuilder primaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
        primaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME, PrimaryKeyValue.fromString(startPkValue));
        rangeRowQueryCriteria.setInclusiveStartPrimaryKey(primaryKeyBuilder.build());
    
        //设置结束主键。
        primaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
        primaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME, PrimaryKeyValue.fromString(endPkValue));
        rangeRowQueryCriteria.setExclusiveEndPrimaryKey(primaryKeyBuilder.build());
    
        rangeRowQueryCriteria.setMaxVersions(1);
    
        System.out.println("GetRange的结果为:");
        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;
            }
        }
    }         
  • 示例2

    按照第一个主键列确定范围、第二主键列从最小值(INF_MIN)到最大值(INF_MAX)进行正序读取,判断nextStartPrimaryKey是否为null,读取完范围内的全部数据。

    private static void getRange(SyncClient client, String startPkValue, String endPkValue) {
        RangeRowQueryCriteria rangeRowQueryCriteria = new RangeRowQueryCriteria(TABLE_NAME);
        //设置起始主键,以两个主键为例。
        PrimaryKeyBuilder primaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
        primaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME1, PrimaryKeyValue.fromString(startPkValue));//确定值。
        primaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME2, PrimaryKeyValue.INF_MIN);//最小值。
        rangeRowQueryCriteria.setInclusiveStartPrimaryKey(primaryKeyBuilder.build());
    
        //设置结束主键。
        primaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
        primaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME1, PrimaryKeyValue.fromString(endPkValue));//确定值。
        primaryKeyBuilder.addPrimaryKeyColumn(PRIMARY_KEY_NAME2, PrimaryKeyValue.INF_MAX);//最大值。
        rangeRowQueryCriteria.setExclusiveEndPrimaryKey(primaryKeyBuilder.build());
    
        rangeRowQueryCriteria.setMaxVersions(1);
    
        System.out.println("GetRange的结果为:");
        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;
            }
        }
    }         
  • 示例3

    读取主键范围为["pk:aaa.xxx.com", "pk:zzz.xxx.com")时Col1列的数据,并对该列的数据执行正则过滤。

    private static void getRange(SyncClient client) {
        RangeRowQueryCriteria criteria = new RangeRowQueryCriteria(TABLE_NAME);
     
        // 设置主键范围为["pk:aaa.xxx.com", "pk:zzz.xxx.com"),读取范围为左闭右开的区间。
        PrimaryKey pk0 = PrimaryKeyBuilder.createPrimaryKeyBuilder()
            .addPrimaryKeyColumn("pk", PrimaryKeyValue.fromString("aaa.xxx.com"))
            .build();
        PrimaryKey pk1 = PrimaryKeyBuilder.createPrimaryKeyBuilder()
            .addPrimaryKeyColumn("pk", PrimaryKeyValue.fromString("zzz.xxx.com"))
            .build();
        criteria.setInclusiveStartPrimaryKey(pk0);
        criteria.setExclusiveEndPrimaryKey(pk1);
     
        // 设置读取最新版本。
        criteria.setMaxVersions(1);
     
        // 设置过滤器,当cast<int>(regex(Col1)) > 100时,返回该行。
        RegexRule regexRule = new RegexRule("t1:([0-9]+),", VariantType.Type.VT_INTEGER);
        SingleColumnValueRegexFilter filter =  new SingleColumnValueRegexFilter("Col1",
            regexRule,SingleColumnValueFilter.CompareOperator.GREATER_THAN,ColumnValue.fromLong(100));
        criteria.setFilter(filter);
    
        while (true) {
            GetRangeResponse resp = client.getRange(new GetRangeRequest(criteria));
            for (Row row : resp.getRows()) {
                // do something
                System.out.println(row);
            }
            if (resp.getNextStartPrimaryKey() != null) {
                criteria.setInclusiveStartPrimaryKey(resp.getNextStartPrimaryKey())
            }
        }
    }