全部產品
Search
文件中心

Tablestore:局部事務

更新時間:Jun 30, 2024

為資料表開啟局部事務後,使用局部事務功能,您可以建立資料範圍在一個分區索引值內的局部事務並對局部事務中的資料進行讀寫操作。通過使用局部事務您可以實現單行或多行讀寫的原子操作。

情境

使用局部事務功能,可以實現單行或多行讀寫的原子操作,擴充了使用情境。 具體情境範例如下:

簡單情境:讀-寫情境

當需要進行讀取-修改-寫回(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介面丟棄局部事務。

此實現流程非最優方案。在此情境中,您可以通過增加新的索引表加速查詢操作。使用局部事務後,無需擔心資料表與索引表的狀態不一致,降低開發難度。例如“統計郵件數量”功能在上面的方案中需要讀取很多封郵件,開銷較大,您可以使用一個新的索引表儲存已讀郵件與未讀郵件的數量,從而降低開銷,加速查詢。

前提條件

已為資料表開啟局部事務。

目前局部事務功能處於邀測中,預設關閉。如果需要使用該功能,請提交工單進行申請。

重要

使用Java SDK 5.11.0及以上版本時,您可以在建立資料表時開啟局部事務。更多資訊,請參見資料表操作

注意事項

  • 主鍵自增列功能和局部事務功能不能同時使用。

  • 局部事務通過悲觀鎖(Pessimistic Lock)實現並發控制。

  • 每個局部事務從建立開始生命週期最長為60秒。

    如果超過60秒未提交局部事務或丟棄局部事務,則Table Store服務端會認為此局部事務逾時,並將局部事務丟棄。

  • 如果建立局部事務時逾時,則請求可能在Table Store服務端已執行成功,此時請等待該局部事務逾時後重新建立。

  • 未提交的局部事務可能會失效,如果出現此情況,則需要重試該局部事務內的操作。

  • 如果未對局部事務範圍內的資料進行寫操作,則提交局部事務或丟棄局部事務的操作是等同的。

  • 在局部事務中讀寫資料有如下限制:

    • 不能使用局部事務ID訪問局部事務範圍(即建立時使用的分區索引值)以外的資料。

    • 同一個局部事務中所有寫請求的分區索引值必須與建立局部事務時的分區索引值相同,讀請求則無此限制。

    • 一個局部事務同時只能用於一個請求中,在使用局部事務期間,其他使用此局部事務ID的操作均會失敗。

    • 每個局部事務中兩次讀寫操作的最大間隔為60秒。

      如果超過60秒未操作局部事務,則Table Store服務端會認為此局部事務逾時,並將局部事務丟棄。

    • 每個局部事務中寫入的資料量最大為4 MB,按正常的寫請求資料量計算規則累加。

    • 如果在局部事務中寫入了未指定版本號碼的Cell,則該Cell的版本號碼會在寫入資料時(而非提交局部事務時)由Table Store服務端自動產生,建置規則與正常寫入一個未指定版本號碼的Cell相同。

    • 如果BatchWriteRow請求中帶有局部事務ID,則此請求中所有行只能操作該局部事務ID對應的表。

    • 在使用局部事務期間,對應分區索引值的資料會被加上寫鎖,只有持有局部事務ID在局部事務範圍內的寫請求才會成功。其他非事務請求或持有其他局部事務ID在局部事務範圍內的寫請求均會失敗。在局部事務提交、丟棄或逾時後,對應的鎖也會被釋放。

    • 帶有局部事務ID的讀寫請求失敗不會影響局部事務本身的存活情況,您可以指定重試規則進行重試或者主動丟棄當前局部事務。

介面

對局部事務進行操作的介面說明請參見下表。

介面

說明

StartLocalTransaction

建立一個局部事務。

CommitTransaction

提交一個局部事務。

AbortTransaction

丟棄一個局部事務。

您可以使用GetRowPutRowUpdateRowDeleteRowGetRange或者BatchWriteRow介面對局部事務範圍內的資料進行操作。具體操作,請參見寫入資料讀取資料刪除資料

說明

當前局部事務範圍在一個分區索引值內。關於分區鍵的更多資訊,請參見分區鍵

使用方式

使用局部事務時,您需要先建立資料範圍在一個分區索引值內的局部事務,然後對局部事務中的資料進行讀寫操作,最後根據實際提交或者丟棄局部事務。

重要

只支援通過SDK方式使用局部事務功能。

您可以通過Java SDKGo SDKPython SDKNode.js SDKPHP SDK使用局部事務功能。此處以Java SDK為例介紹使用局部事務讀寫資料的操作。

使用局部事務寫入一行資料

以下樣本用於為表的指定分區鍵建立一個局部事務後,在局部事務內寫入一行資料。

private static void transactionPutRow(SyncClient client) {
    //設定資料表名稱。
    String tableName="<TABLE_NAME>";
    //使用指定分區索引值建立一個局部事務。 
    PrimaryKeyBuilder primaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
    //設定第一個主鍵(即分區鍵)的列名、資料類型和列值。
    primaryKeyBuilder.addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString("pkvalue"));
    PrimaryKey primaryKey = primaryKeyBuilder.build();
    //構造建立局部事務的請求。
    StartLocalTransactionRequest request = new StartLocalTransactionRequest(tableName, primaryKey);
    //發起建立局部事務的請求並擷取局部事務ID。
    String txnId = client.startLocalTransaction(request).getTransactionID();
    
    //在局部事務內寫入一行資料。
    //構造行的主鍵資訊。
    PrimaryKeyBuilder primaryKeyBuilder1 = PrimaryKeyBuilder.createPrimaryKeyBuilder();
    //設定主鍵的列名、資料類型和列值。如果表的主鍵由多個主鍵列組成,則多個主鍵列需要按照順序依次進行配置。
    primaryKeyBuilder1.addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString("pkvalue"));
    primaryKeyBuilder1.addPrimaryKeyColumn("pk2", PrimaryKeyValue.fromLong(10001));
    PrimaryKey primaryKey1 = primaryKeyBuilder1.build();
    //構造行的屬性列。
    RowPutChange rowPutChange = new RowPutChange(tableName, primaryKey1);
    //設定屬性列的列名、資料類型和列值,請根據需要添加所需屬性列。
    rowPutChange.addColumn(new Column("col1", ColumnValue.fromString("colvalue")));
    rowPutChange.addColumn(new Column("col2", ColumnValue.fromLong(10)));
    PutRowRequest request1 = new PutRowRequest(rowPutChange);
    //設定局部事務ID到請求中。
    request1.setTransactionId(txnId);
    client.putRow(request1);
    
    //提交或丟棄局部事務。 
    //提交局部事務,使局部事務中的所有資料修改生效。
    CommitTransactionRequest commitRequest = new CommitTransactionRequest(txnId);
    client.commitTransaction(commitRequest);
    //丟棄局部事務,局部事務中的所有資料修改均不會應用到原有資料。
    //AbortTransactionRequest abortRequest = new AbortTransactionRequest(txnId);
    //client.abortTransaction(abortRequest);
}

使用局部事務讀取一行資料

以下樣本用於為表的指定分區鍵建立一個局部事務後,在局部事務內讀取一行資料。

private static void transactionGetRow(SyncClient client) {
    //設定資料表名稱。
    String tableName="exampletabled";
    //使用指定分區索引值建立一個局部事務。
    PrimaryKeyBuilder primaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
    //設定第一個主鍵(即分區鍵)的列名、資料類型和列值。
    primaryKeyBuilder.addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString("111"));
    PrimaryKey primaryKey = primaryKeyBuilder.build();
    //構造建立局部事務的請求。
    StartLocalTransactionRequest request = new StartLocalTransactionRequest(tableName, primaryKey);
    //發起建立局部事務的請求並擷取局部事務ID。
    String txnId = client.startLocalTransaction(request).getTransactionID();

    //在局部事務內讀取一行資料。
    //構造行的主鍵資訊。
    PrimaryKeyBuilder primaryKeyBuilder1 = PrimaryKeyBuilder.createPrimaryKeyBuilder();
    //設定主鍵的列名、資料類型和列值。如果表的主鍵由多個主鍵列組成,則多個主鍵列需要按照順序依次進行配置。
    primaryKeyBuilder1.addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString("111"));
    primaryKeyBuilder1.addPrimaryKeyColumn("pk2", PrimaryKeyValue.fromLong(10001));
    PrimaryKey primaryKey1 = primaryKeyBuilder1.build();
    SingleRowQueryCriteria criteria = new SingleRowQueryCriteria(tableName, primaryKey1);
    //設定讀取最新版本的資料。
    criteria.setMaxVersions(1);
    GetRowRequest request1 = new GetRowRequest(criteria);
    //設定局部事務ID到請求中。
    request1.setTransactionId(txnId);
    GetRowResponse getRowResponse = client.getRow(request1);
  
    //提交或丟棄局部事務。對於讀操作來說,提交局部事務或丟棄局部事務的操作是等同的。
    //提交局部事務,使局部事務中的所有資料修改生效。
    CommitTransactionRequest commitRequest = new CommitTransactionRequest(txnId);
    client.commitTransaction(commitRequest);
    //丟棄局部事務,局部事務中的所有資料修改均不會應用到原有資料。
    //AbortTransactionRequest abortRequest = new AbortTransactionRequest(txnId);
    //client.abortTransaction(abortRequest);
    
    //擷取並列印行資料。
    Row row = getRowResponse.getRow();
    System.out.println("讀取完畢,結果為:");
    System.out.println(row);
}

計費說明

  • StartLocalTransaction、CommitTransaction和AbortTransaction操作分別消耗1單位寫CU。

  • 讀寫操作的計費與正常的讀寫請求相同。關於計費的更多資訊,請參見計費概述

錯誤碼

錯誤碼

說明

OTSRowOperationConflict

該分區索引值已被其他局部事務佔用。

OTSSessionNotExist

事務ID對應的事務不存在,或該事務已失效或逾時。

OTSSessionBusy

該事務的上一次請求尚未結束。

OTSOutOfTransactionDataSizeLimit

事務內的資料量超過上限。

OTSDataOutOfRange

使用者操作資料的分區鍵超出了事務建立的分區鍵範圍。