このページでは、Spanner クライアント ライブラリを使用してトランザクションのタイムアウトを設定する方法について説明します。指定されたタイムアウト値内にトランザクションを完了できない場合、トランザクションは DEADLINE_EXCEEDED
エラーで失敗します。
トランザクションと RPC リクエスト ステートメントのタイムアウト値を設定できます。トランザクションのタイムアウト値をそのトランザクションで実行されるステートメントのタイムアウト値よりも長く設定しても、ステートメントのタイムアウトは増加しません(ステートメントのタイムアウトは独自のタイムアウト値によって制約されます)。
また、Commit
リクエストの実行中にタイムアウト エラーが発生した場合は、トランザクションが commit された可能性もあります。
トランザクション タイムアウトは、Go クライアント ライブラリと Java クライアント ライブラリを使用して設定できます。
Go
import (
"context"
"errors"
"fmt"
"io"
"time"
"cloud.google.com/go/spanner"
"google.golang.org/api/iterator"
"google.golang.org/grpc/codes"
)
func transactionTimeout(w io.Writer, db string) error {
ctx := context.Background()
client, err := spanner.NewClient(ctx, db)
if err != nil {
return err
}
defer client.Close()
// Create a context with a 60-second timeout and use this context to run a read/write transaction.
// This context timeout will be applied to the entire transaction, and the transaction will fail
// if it cannot finish within the specified timeout value. The Spanner client library applies the
// (remainder of the) timeout to each statement that is executed in the transaction.
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
_, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
selectStmt := spanner.Statement{
SQL: `SELECT SingerId, FirstName, LastName FROM Singers ORDER BY LastName, FirstName`,
}
// The context that is passed in to the transaction function should be used for each statement
// is executed.
iter := txn.Query(ctx, selectStmt)
defer iter.Stop()
for {
row, err := iter.Next()
if errors.Is(err, iterator.Done) {
break
}
if err != nil {
return err
}
var singerID int64
var firstName, lastName string
if err := row.Columns(&singerID, &firstName, &lastName); err != nil {
return err
}
fmt.Fprintf(w, "%d %s %s\n", singerID, firstName, lastName)
}
stmt := spanner.Statement{
SQL: `INSERT INTO Singers (SingerId, FirstName, LastName)
VALUES (38, 'George', 'Washington')`,
}
rowCount, err := txn.Update(ctx, stmt)
if err != nil {
return err
}
fmt.Fprintf(w, "%d record(s) inserted.\n", rowCount)
return nil
})
// Check if an error was returned by the transaction.
// The spanner.ErrCode(err) function will return codes.OK if err == nil.
code := spanner.ErrCode(err)
if code == codes.OK {
fmt.Fprintf(w, "Transaction with timeout was executed successfully\n")
} else if code == codes.DeadlineExceeded {
fmt.Fprintf(w, "Transaction timed out\n")
} else {
fmt.Fprintf(w, "Transaction failed with error code %v\n", code)
}
return err
}
Java
import com.google.cloud.spanner.DatabaseClient;
import com.google.cloud.spanner.DatabaseId;
import com.google.cloud.spanner.ResultSet;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.Statement;
import io.grpc.Context;
import io.grpc.Context.CancellableContext;
import io.grpc.Deadline;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Sample showing how to set a timeout for an entire transaction for the Cloud Spanner Java client.
*/
class TransactionTimeoutExample {
static void executeTransactionWithTimeout() {
// TODO(developer): Replace these variables before running the sample.
String projectId = "my-project";
String instanceId = "my-instance";
String databaseId = "my-database";
executeTransactionWithTimeout(projectId, instanceId, databaseId, 60L, TimeUnit.SECONDS);
}
// Execute a read/write transaction with a timeout for the entire transaction.
static void executeTransactionWithTimeout(
String projectId,
String instanceId,
String databaseId,
long timeoutValue,
TimeUnit timeoutUnit) {
try (Spanner spanner = SpannerOptions.newBuilder().setProjectId(projectId).build()
.getService()) {
DatabaseClient client =
spanner.getDatabaseClient(DatabaseId.of(projectId, instanceId, databaseId));
// Create a gRPC context with a deadline and with cancellation.
// gRPC context deadlines require the use of a scheduled executor.
ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
try (CancellableContext context =
Context.current()
.withDeadline(Deadline.after(timeoutValue, timeoutUnit), executor)
.withCancellation()) {
context.run(
() -> {
client
.readWriteTransaction()
.run(
transaction -> {
try (ResultSet resultSet =
transaction.executeQuery(
Statement.of(
"SELECT SingerId, FirstName, LastName\n"
+ "FROM Singers\n"
+ "ORDER BY LastName, FirstName"))) {
while (resultSet.next()) {
System.out.printf(
"%d %s %s\n",
resultSet.getLong("SingerId"),
resultSet.getString("FirstName"),
resultSet.getString("LastName"));
}
}
String sql =
"INSERT INTO Singers (SingerId, FirstName, LastName)\n"
+ "VALUES (20, 'George', 'Washington')";
long rowCount = transaction.executeUpdate(Statement.of(sql));
System.out.printf("%d record inserted.%n", rowCount);
return null;
});
});
}
}
}
}