After you enable the local transaction feature for a data table, you can create a local transaction based on the specified partition key value and perform read and write operations on the data in the local transaction. You can use the local transaction feature to perform atomic operations to read and write one or more rows.
Scenarios
The local transaction feature allows you to perform atomic operations to read and write one or more rows. This section describes the sample scenarios of the feature:
Simple scenarios: Read and write data
You can use the following two methods to perform read, modify, and write (RMW) operations. The two methods each has specific limits.
Conditional update: processes only one request that involves a single row at a time. You cannot use this method to process requests that involve multiple data rows or requests that involve multiple write operations. For more information, see Conditional updates.
Atomic counter: processes only one request that involves a single row at a time, and supports only the increment of column values. For more information, see Atomic counters.
To resolve the preceding issues, you can create a local transaction to perform RMW operations on data within the range specified by a partition key value.
Call the StartLocalTransaction operation to create a local transaction based on the specified partition key value and obtain the local transaction ID.
Call the GetRow or GetRange operation to read data. The request must contain the local transaction ID.
Modify data on the client.
Call the PutRow, UpdateRow, DeleteRow, or BatchWriteRow operation to write back the modified data. The request must contain the local transaction ID.
Call the CommitTransaction operation to commit the local transaction.
Complex scenarios: Manage emails
You can create a local transaction to perform atomic operations on the emails of a specific user.
To use the local transaction feature, include two index tables in a data table. The following table describes the primary key columns of the data table and index tables. The Type column is used to identify the rows of the data table and those of the index tables. The IndexField column stores the $Folder field for the Folder index table, and the $SendTime field for the SendTime index table. The IndexField column is empty for the data table.
Table | Primary key column | |||
UserID | Type | IndexField | MailID | |
Data table | User ID | "Main" | N/A | Email ID |
Folder index table | User ID | "Folder" | $Folder | Email ID |
SendTime index table | User ID | "SendTime" | $SendTime | Email ID |
Specifically, you can use the local transaction feature to perform the following operations on emails.
Scenario 1: List the last 100 emails sent by a user
Use the user ID to create a local transaction and obtain the local transaction ID.
Call the GetRange operation to query 100 emails from the SendTime index table. The request must contain the local transaction ID.
Call the BatchGetRow operation to query the detailed information of the 100 emails from the data table. The request must contain the local transaction ID.
Call the CommitTransaction operation to commit the local transaction, or call the AbortTransaction operation to abort the local transaction.
Scenario 2: Transfer all emails in a folder to another folder
Use the user ID to create a local transaction and obtain the local transaction ID.
Call the GetRange operation to query emails from the Folder index table. The request must contain the local transaction ID.
Call the BatchWriteRow operation to perform write operations on the Folder index table. The request must contain the local transaction ID.
A write operation is performed on two rows each time when an email is transferred. Specifically, a row that indicates the original folder is removed from the Folder index table, and a row that indicates the new folder is added to the Folder index table.
Call the CommitTransaction operation to commit the local transaction.
Scenario 3: Count the numbers of read emails and unread emails in a folder
Use the user ID to create a local transaction and obtain the local transaction ID.
Call the GetRange operation to query emails from the Folder index table. The request must contain the local transaction ID.
Call the BatchGetRow operation to query the read status of each email from the data table.
Call the CommitTransaction operation to commit the local transaction, or call the AbortTransaction operation to abort the local transaction.
This solution is not optimal. In this scenario, you can add more index tables to accelerate queries. The local transaction feature ensures status consistency between the data table and the index tables. This simplifies development. For example, when you use this solution to count the number of emails, a large number of emails need to be read. This causes high overheads. To reduce overheads and accelerate queries, you can use a new index table to store the numbers of read emails and unread emails.
Before you begin
Make sure that the local transaction feature is enabled for the data table to manage.
The local transaction feature is available for invitational preview. By default, this feature is disabled. To use the local transaction feature, submit a ticket.
If you use Tablestore SDK for Java V5.11.0 or later, you can enable the local transaction feature when you create a data table. For more information, see Operations on tables.
Usage notes
You cannot use the auto-increment primary key column feature and the local transaction feature at the same time.
Pessimistic locking is used to control concurrent operations in a local transaction.
The validity period of a local transaction can be up to 60 seconds.
If a local transaction is not committed or aborted within 60 seconds, the Tablestore server determines that the local transaction times out and aborts the transaction.
A transaction may be created on the Tablestore server even if a timeout error is returned. In this case, you can resend a transaction creation request after the created transaction times out.
If a local transaction is not committed, it may become invalid. In this case, retry the operations in this transaction.
If no write operation is performed on the data in a local transaction, the commit and abort operations have the same effect.
Tablestore imposes the following limits on read and write operations on the data in a local transaction:
The local transaction ID cannot be used to access data beyond the range specified based on the partition key value that is used to create the transaction.
The partition key values of all write requests in the same transaction must be the same as the partition key value used to create the transaction. This limit does not apply to read requests.
A local transaction can be used by only one request at a time. When the local transaction is in use, other operations that use the same local transaction ID fail.
The maximum interval for two consecutive read or write operations on the data in a local transaction is 60 seconds.
If no read or write operation is performed on the data in a local transaction for more than 60 seconds, the Tablestore server determines that the transaction times out and aborts the transaction.
Up to 4 MB of data can be written to each transaction. The volume of data written to each transaction is calculated in the same way as a regular write request.
If you do not specify a version number for a cell, the Tablestore server automatically assigns a version number to the cell in the usual way when the cell is written to the transaction rather than the way when the transaction is committed.
If a BatchWriteRow request includes a local transaction ID, all rows in the request can be written only to the table that matches the local transaction ID.
When you use a local transaction, a write lock is added to the data of the partition key value based on which the local transaction is created. Only write requests that contain the local transaction ID and are initiated to write data in the local transaction can be successful. Other non-transactional requests or write requests that contain the IDs of other local transactions and are initiated to write data in the local transaction will fail. Data in the local transaction is unlocked if the transaction is committed or aborted, or if the transaction times out.
A local transaction remains valid even if a read or write request with the local transaction ID is rejected. You can specify a retry rule to resend the request, or you can abort the transaction.
API operations
The following table describes the API operations that can be called to manage local transactions.
API operation | Description |
StartLocalTransaction | Creates a local transaction. |
CommitTransaction | Commits a local transaction. |
AbortTransaction | Aborts a local transaction. |
You can call the GetRow, PutRow, UpdateRow, DeleteRow, GetRange, or BatchWriteRow operation to manage the data in a local transaction. For more information, see Write data, Read data, and Delete data.
Rows in a local transaction share the same partition key value. For more information about partition keys, see the Partition keys section of the "Primary keys and attributes" topic.
Use the local transaction feature
Before you use the local transaction feature, you must create a local transaction based on a partition key value. Then, you can perform read and write operations on the data in the local transaction. After you perform operations, you can commit or abort the local transaction as needed.
You can manage local transactions only by using Tablestore SDKs.
The following Tablestore SDKs are supported: Tablestore SDK for Java, Tablestore SDK for Go, Tablestore SDK for Python, Tablestore SDK for Node.js, and Tablestore SDK for PHP. The following examples show how to use the local transaction feature to perform read and write operations. In the examples, Tablestore SDK for Java is used.
Use the local transaction feature to write a data row
The following sample code shows how to create a local transaction based on the specified partition key value in a table and how to write a data row in the local transaction:
private static void transactionPutRow(SyncClient client) {
// Specify the name of the data table.
String tableName="<TABLE_NAME>";
// Create a local transaction based on the specified partition key value.
PrimaryKeyBuilder primaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
// Specify the column name, data type, and column value of the partition key.
primaryKeyBuilder.addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString("pkvalue"));
PrimaryKey primaryKey = primaryKeyBuilder.build();
// Construct a request to create a local transaction.
StartLocalTransactionRequest request = new StartLocalTransactionRequest(tableName, primaryKey);
// Initiate the request to create a local transaction and obtain the local transaction ID.
String txnId = client.startLocalTransaction(request).getTransactionID();
// Write a data row in the local transaction.
// Configure the primary key information of the row.
PrimaryKeyBuilder primaryKeyBuilder1 = PrimaryKeyBuilder.createPrimaryKeyBuilder();
// Specify the column name, data type, and column value of the primary key. If the primary key of the table consists of multiple primary key columns, configure the information about the primary key columns in sequence.
primaryKeyBuilder1.addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString("pkvalue"));
primaryKeyBuilder1.addPrimaryKeyColumn("pk2", PrimaryKeyValue.fromLong(10001));
PrimaryKey primaryKey1 = primaryKeyBuilder1.build();
// Configure the attribute columns of the row.
RowPutChange rowPutChange = new RowPutChange(tableName, primaryKey1);
// Specify the column name, data type, and column value of each attribute column. Add more attribute columns as required.
rowPutChange.addColumn(new Column("col1", ColumnValue.fromString("colvalue")));
rowPutChange.addColumn(new Column("col2", ColumnValue.fromLong(10)));
PutRowRequest request1 = new PutRowRequest(rowPutChange);
// Pass the local transaction ID to the request.
request1.setTransactionId(txnId);
client.putRow(request1);
// Commit or abort the local transaction.
// Commit the local transaction so that all data modifications in the local transaction take effect.
CommitTransactionRequest commitRequest = new CommitTransactionRequest(txnId);
client.commitTransaction(commitRequest);
// Abort the local transaction so that all data modifications in the local transaction do not take effect.
//AbortTransactionRequest abortRequest = new AbortTransactionRequest(txnId);
//client.abortTransaction(abortRequest);
}
Use the local transaction feature to read a data row
The following sample code shows how to create a local transaction based on the specified partition key value in a table and how to read a data row in the local transaction:
private static void transactionGetRow(SyncClient client) {
// Specify the name of the data table.
String tableName="exampletabled";
// Create a local transaction based on the specified partition key value.
PrimaryKeyBuilder primaryKeyBuilder = PrimaryKeyBuilder.createPrimaryKeyBuilder();
// Specify the column name, data type, and column value of the partition key.
primaryKeyBuilder.addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString("111"));
PrimaryKey primaryKey = primaryKeyBuilder.build();
// Construct a request to create a local transaction.
StartLocalTransactionRequest request = new StartLocalTransactionRequest(tableName, primaryKey);
// Initiate the request to create a local transaction and obtain the local transaction ID.
String txnId = client.startLocalTransaction(request).getTransactionID();
// Read a data row in the local transaction.
// Configure the primary key information of the row.
PrimaryKeyBuilder primaryKeyBuilder1 = PrimaryKeyBuilder.createPrimaryKeyBuilder();
// Specify the column name, data type, and column value of the primary key. If the primary key of the table consists of multiple primary key columns, configure the information about the primary key columns in sequence.
primaryKeyBuilder1.addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString("111"));
primaryKeyBuilder1.addPrimaryKeyColumn("pk2", PrimaryKeyValue.fromLong(10001));
PrimaryKey primaryKey1 = primaryKeyBuilder1.build();
SingleRowQueryCriteria criteria = new SingleRowQueryCriteria(tableName, primaryKey1);
// Read the latest version of data.
criteria.setMaxVersions(1);
GetRowRequest request1 = new GetRowRequest(criteria);
// Pass the local transaction ID to the request.
request1.setTransactionId(txnId);
GetRowResponse getRowResponse = client.getRow(request1);
// Commit or abort the local transaction. For a read operation, committing or aborting a local transaction has the same effect.
// Commit the local transaction so that all data modifications in the local transaction take effect.
CommitTransactionRequest commitRequest = new CommitTransactionRequest(txnId);
client.commitTransaction(commitRequest);
// Abort the local transaction so that all data modifications in the local transaction do not take effect.
//AbortTransactionRequest abortRequest = new AbortTransactionRequest(txnId);
//client.abortTransaction(abortRequest);
// Obtain and display the data row.
Row row = getRowResponse.getRow();
System.out.println("The data row is read and displayed as follows:");
System.out.println(row);
}
Billing
Each StartLocalTransaction, CommitTransaction, and AbortTransaction operation consumes one write capacity unit (CU).
You are charged for read and write operations on the data in a local transaction in the same way as you are charged when you perform common read and write operations on data. For more information about billing, see Billing overview.
Error codes
Error code | Description |
OTSRowOperationConflict | The partition key value is already occupied by another local transaction. |
OTSSessionNotExist | The transaction corresponding to the specified transaction ID does not exist, or the transaction is invalid or timed out. |
OTSSessionBusy | The last request on the transaction is incomplete. |
OTSOutOfTransactionDataSizeLimit | The amount of data in the transaction exceeds the upper limit. |
OTSDataOutOfRange | The data operation is beyond the range specified by the partition key value used to create the transaction. |