使用局部事务功能,创建数据范围在一个分区键值内的局部事务。对局部事务中的数据进行读写操作后,可以根据实际提交或者丢弃局部事务。

目前局部事务功能处于邀测中,默认关闭。如果需要使用该功能,请提交工单进行申请或者加入钉钉群23307953(表格存储技术交流群-2)进行咨询。

使用局部事务功能,可以实现单行或多行读写的原子操作,扩展了使用场景。

场景

  • 读-写场景(简单场景)
    当需要进行读取-修改-写回(Read-Modify-Write)操作时,可以选择如下两种方式,但两种方式有一些限制。
    • 条件更新:只能处理单行单次请求,不能处理数据分布在多行,或者需要多次写入的情况,详情请参见条件更新
    • 原子计数器:只能处理单行单次请求,且只能进行列值的累加操作,详情请参见原子计数器
    使用局部事务可以实现一个分区键值范围内的通用读取-修改-写回流程。
    1. 使用StartLocalTransaction为分区键值创建一个局部事务,并获取局部事务ID。
    2. 使用GetRow或GetRange接口获取数据,且请求中需要带上局部事务ID。
    3. 客户端本地修改数据。
    4. 使用PutRow、UpdateRow、DeleteRow或BatchWriteRow接口将修改后的数据写回,且请求中需要带上事务ID。
    5. 使用CommitTransaction提交局部事务。
  • 邮箱场景(复杂场景)

    使用局部事务可以实现对同一个用户邮件的原子操作。

    为了能正常使用局部事务功能,在一张物理表上同时使用了一张主表和两张索引表,其主键列请参见下表。

    其中使用Type列区分主表和不同的索引表,不同的索引行使用IndexField列保存不同含义的字段,而主表无IndexField列。

    UserID Type IndexField MailID
    主表 用户ID "Main" "N/A" 邮件ID
    Folder表 用户ID "Folder" $Folder 邮件ID
    SendTime表 用户ID "SendTime" $SendTime 邮件ID

    使用局部事务功能可以完成如下操作。

    • 列出某个用户发送的最近100封邮件,操作步骤如下:
      1. 使用UserID创建一个局部事务,并获取局部事务ID。
      2. 使用GetRange接口从SendTime表获取100封邮件,且请求中需要带上局部事务ID。
      3. 使用BatchGetRow接口从主表获取100封邮件的详细信息,且请求中需要带上局部事务ID。
      4. 使用CommitTransaction提交局部事务或者使用AbortTransaction丢弃局部事务。

        由于未对该局部事务进行写操作,所以提交或丢弃局部事务的操作是等同的。

    • 将某个目录下的所有邮件移到另一个目录下,操作步骤如下:
      1. 使用UserID创建一个局部事务,并获取局部事务ID。
      2. 使用GetRange接口从Folder表获取若干封邮件,且请求中需要带上局部事务ID。
      3. 使用BatchWriteRow接口对Folder表进行写操作,且请求中需要带上局部事务ID。

        每封邮件对应两行写操作,一行是将对应旧Folder的行删掉,另一行是对应新Folder增加一行。

      4. 使用CommitTransaction提交局部事务。
    • 统计某个目录下已读邮件与未读邮件的数量(非最优方案,详情说明请参见下文),操作步骤如下:
      1. 使用UserID创建一个局部事务,并获取局部事务ID。
      2. 使用GetRange接口从Folder表获取若干封邮件,且请求中需要带上局部事务ID。
      3. 使用BatchGetRow接口从主表获取每封邮件的已读状态。
      4. 使用CommitTransaction提交局部事务或者使用AbortTransaction丢弃局部事务。

        由于未对该局部事务进行写操作,所以提交或丢弃局部事务的操作是等同的。

    在此场景中,可以通过增加新的索引表加速常用操作。使用局部事务后,无需担心主表与索引表的状态不一致,降低开发难度。例如“统计邮件数量”功能在上面的方案中需要读取很多封邮件,开销较大,可以使用一个新的索引表保存已读和未读邮件的数量,从而降低开销,加速查询。

限制

  • 每个局部事务从创建开始生命周期最长为60秒。

    如果超过60秒未提交或丢弃局部事务,表格存储服务端会认为此局部事务超时,并将局部事务丢弃。

  • 如果创建局部事务时超时,此请求可能在表格存储服务端已执行成功,此时用户需要等待该局部事务超时后重新创建。
  • 未提交的局部事务可能失效,如果出现此情况,需要重试该局部事务内的操作。
  • 在局部事务中读写数据有如下限制:
    • 不能使用局部事务ID访问局部事务范围(即创建时使用的分区键值)以外的数据。
    • 同一个局部事务中所有写请求的分区键值必须与创建局部事务时的分区键值相同,读请求则无此限制。
    • 一个局部事务同时只能用于一个请求中,在使用局部事务期间,其它使用此局部事务ID的操作均会失败。
    • 每个局部事务中两次读写操作的最大间隔为60秒。

      如果超过60秒未操作局部事务,表格存储服务端会认为此局部事务超时,并将局部事务丢弃。

    • 每个局部事务中写入的数据量最大为4 MB,按正常的写请求数据量计算规则累加。
    • 如果在局部事务中写入了未指定版本号的Cell,该Cell的版本号会在写入时(而非提交时)由表格存储服务端自动生成,生成规则与正常写入一个未指定版本号的Cell相同。
    • 如果BatchWriteRow请求中带有局部事务ID,则此请求中所有行只能操作该局部事务ID对应的表。
    • 在使用局部事务期间,对应分区键值的数据相当于被锁上,只有持有局部事务ID在局部事务范围内的写请求才会成功,其它不持有局部事务ID在局部事务范围内的写请求均会失败。在局部事务提交、丢弃或超时后,对应的锁也会被释放。
    • 带有局部事务ID的读写请求失败不会影响局部事务本身的存活情况,您可以按照正常的无局部事务ID的读写请求重试规则进行重试,或者主动丢弃当前局部事务。

接口

支持对局部事务进行操作的接口请参见下表。
接口 说明
StartLocalTransaction 创建一个局部事务。
CommitTransaction 提交一个局部事务。
AbortTransaction 丢弃一个局部事务。
GetRow 对局部事务范围内的数据进行读写操作,详情请参见基础数据操作
说明 当前局部事务范围在一个分区键值内,分区键的详情请参见主键和属性
PutRow
UpdateRow
DeleteRow
BatchWriteRow
GetRange

使用

您可以使用如下语言的SDK实现局部事务功能。

参数

参数 说明
TableName 数据表名称。
PrimaryKey 数据表主键。
  • 创建局部事务时,只需要指定局部事务对应的分区键值。
  • 创建局部事务后,对局部事务范围内的数据进行读写操作时,需要指定完整主键。
TransactionId 局部事务ID,用于唯一标识一个局部事务。

创建局部事务后,操作局部事务时均需要带上局部事务ID。

示例

  1. 调用AsyncClient或SyncClient的startLocalTransaction方法使用指定分区键值创建一个局部事务,并获取局部事务ID。
    PrimaryKeyBuilder primaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
    primaryKeyBuilder.addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString("txnKey"));
    PrimaryKey primaryKey = primaryKeyBuilder.build();
    StartLocalTransactionRequest request = new StartLocalTransactionRequest(tableName, primaryKey);
    String txnId = client.startLocalTransaction(request).getTransactionID();
  2. 对局部事务范围内的数据进行读写操作。

    对局部事务范围内数据的读写操作与正常读写数据操作基本相同,只需填入局部事务ID即可。

    • 写入一行数据。
      PrimaryKeyBuilder primaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
      primaryKeyBuilder.addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString("txnKey"));
      primaryKeyBuilder.addPrimaryKeyColumn("pk2", PrimaryKeyValue.fromLong("userId"));
      PrimaryKey primaryKey = primaryKeyBuilder.build();
      RowPutChange rowPutChange = new RowPutChange(tableName, primaryKey);
      rowPutChange.addColumn(new Column("Col", ColumnValue.fromLong(columnValue)));
      
      PutRowRequest request = new PutRowRequest(rowPutChange);
      request.setTransactionId(txnId);
      client.putRow(request);
    • 读取此行数据。
      PrimaryKeyBuilder primaryKeyBuilder;
      primaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
      primaryKeyBuilder.addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString("txnKey"));
      primaryKeyBuilder.addPrimaryKeyColumn("pk2", PrimaryKeyValue.fromLong("userId"));
      PrimaryKey primaryKey = primaryKeyBuilder.build();
      SingleRowQueryCriteria criteria = new SingleRowQueryCriteria(tableName, primaryKey);
      criteria.setMaxVersions(1); //设置读取最新版本的数据。
      
      GetRowRequest  request = new GetRowRequest(criteria);
      request.setTransactionId(txnId);
      GetRowResponse getRowResponse = client.getRow(request);
  3. 提交或丢弃局部事务。
    • 提交局部事务,使局部事务中的所有数据修改生效。
      CommitTransactionRequest commitRequest = new CommitTransactionRequest(txnId);
      client.commitTransaction(commitRequest);
    • 丢弃局部事务,局部事务中的所有数据修改均不会应用到原有数据。
      AbortTransactionRequest abortRequest = new AbortTransactionRequest(txnId);
      client.abortTransaction(abortRequest);

计费

  • StartLocalTransaction、CommitTransaction和AbortTransaction操作分别消耗1单位写CU。
  • 读写操作的计费与正常的读写请求相同,计费的详情请参见计费概述

错误码

错误码 说明
OTSRowOperationConflict 该分区键值已被其它局部事务占用。
OTSSessionNotExist 事务ID对应的事务不存在,或该事务已失效或超时。
OTSSessionBusy 该事务的上一次请求尚未结束。
OTSOutOfTransactionDataSizeLimit 事务内的数据量超过上限。
OTSDataOutOfRange 用户操作数据的分区键超出了事务创建的分区键范围。