本頁說明如何在 Spanner 中使用可重複讀取隔離。
可重複讀取是一種隔離層級,可確保交易中的所有讀取作業,都會看到交易開始時資料庫的一致快照。在 Spanner 中,這項隔離等級是透過一種技術實作,這種技術也常稱為快照隔離。在讀寫並行程度高的情況下,許多交易會讀取其他交易可能修改的資料,因此這個方法很有幫助。使用固定快照時,可重複讀取會避免更嚴格的可序列化隔離層級對效能造成影響。讀取作業可執行,不必取得鎖定,也不會封鎖並行的寫入作業,因此因序列化衝突而可能需要重試的交易較少。詳情請參閱隔離等級總覽。
設定隔離等級
您可以使用下列方法,在交易層級設定讀寫交易的隔離等級:
用戶端程式庫
Go
import (
"context"
"fmt"
"io"
"cloud.google.com/go/spanner"
pb "cloud.google.com/go/spanner/apiv1/spannerpb"
)
func writeWithTransactionUsingIsolationLevel(w io.Writer, db string) error {
ctx := context.Background()
// The isolation level specified at the client-level will be applied
// to all RW transactions.
cfg := spanner.ClientConfig{
TransactionOptions: spanner.TransactionOptions{
IsolationLevel: pb.TransactionOptions_SERIALIZABLE,
},
}
client, err := spanner.NewClientWithConfig(ctx, db, cfg)
if err != nil {
return fmt.Errorf("failed to create client: %w", err)
}
defer client.Close()
// The isolation level specified at the transaction-level takes
// precedence over the isolation level configured at the client-level.
// REPEATABLE_READ is used here to demonstrate overriding the client-level setting.
txnOpts := spanner.TransactionOptions{
IsolationLevel: pb.TransactionOptions_REPEATABLE_READ,
}
_, err = client.ReadWriteTransactionWithOptions(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
// Read the current album title
key := spanner.Key{1, 1}
row, err := txn.ReadRow(ctx, "Albums", key, []string{"AlbumTitle"})
if err != nil {
return fmt.Errorf("failed to read album: %v", err)
}
var title string
if err := row.Column(0, &title); err != nil {
return fmt.Errorf("failed to get album title: %v", err)
}
fmt.Fprintf(w, "Current album title: %s\n", title)
// Update the album title
stmt := spanner.Statement{
SQL: `UPDATE Albums
SET AlbumTitle = @AlbumTitle
WHERE SingerId = @SingerId AND AlbumId = @AlbumId`,
Params: map[string]interface{}{
"SingerId": 1,
"AlbumId": 1,
"AlbumTitle": "New Album Title",
},
}
count, err := txn.Update(ctx, stmt)
if err != nil {
return fmt.Errorf("failed to update album: %v", err)
}
fmt.Fprintf(w, "Updated %d record(s).\n", count)
return nil
}, txnOpts)
if err != nil {
return fmt.Errorf("transaction failed: %v", err)
}
return nil
}
Java
static void isolationLevelSetting(DatabaseId db) {
// The isolation level specified at the client-level will be applied to all
// RW transactions.
DefaultReadWriteTransactionOptions transactionOptions =
DefaultReadWriteTransactionOptions.newBuilder()
.setIsolationLevel(IsolationLevel.SERIALIZABLE)
.build();
SpannerOptions options =
SpannerOptions.newBuilder()
.setDefaultTransactionOptions(transactionOptions)
.build();
Spanner spanner = options.getService();
DatabaseClient dbClient = spanner.getDatabaseClient(db);
dbClient
// The isolation level specified at the transaction-level takes precedence
// over the isolation level configured at the client-level.
.readWriteTransaction(Options.isolationLevel(IsolationLevel.REPEATABLE_READ))
.run(transaction -> {
// Read an AlbumTitle.
String selectSql =
"SELECT AlbumTitle from Albums WHERE SingerId = 1 and AlbumId = 1";
ResultSet resultSet = transaction.executeQuery(Statement.of(selectSql));
String title = null;
while (resultSet.next()) {
title = resultSet.getString("AlbumTitle");
}
System.out.printf("Current album title: %s\n", title);
// Update the title.
String updateSql =
"UPDATE Albums "
+ "SET AlbumTitle = 'New Album Title' "
+ "WHERE SingerId = 1 and AlbumId = 1";
long rowCount = transaction.executeUpdate(Statement.of(updateSql));
System.out.printf("%d record updated.\n", rowCount);
return null;
});
}
Node.js
// Imports the Google Cloud Spanner client library
const {Spanner, protos} = require('@google-cloud/spanner');
// The isolation level specified at the client-level will be applied
// to all RW transactions.
const isolationOptionsForClient = {
defaultTransactionOptions: {
isolationLevel:
protos.google.spanner.v1.TransactionOptions.IsolationLevel.SERIALIZABLE,
},
};
// Instantiates a client with defaultTransactionOptions
const spanner = new Spanner({
projectId: projectId,
defaultTransactionOptions: isolationOptionsForClient,
});
function runTransactionWithIsolationLevel() {
// Gets a reference to a Cloud Spanner instance and database
const instance = spanner.instance(instanceId);
const database = instance.database(databaseId);
// The isolation level specified at the request level takes precedence over the isolation level configured at the client level.
const isolationOptionsForTransaction = {
isolationLevel:
protos.google.spanner.v1.TransactionOptions.IsolationLevel
.REPEATABLE_READ,
};
database.runTransaction(
isolationOptionsForTransaction,
async (err, transaction) => {
if (err) {
console.error(err);
return;
}
try {
const query =
'SELECT AlbumTitle FROM Albums WHERE SingerId = 1 AND AlbumId = 1';
const results = await transaction.run(query);
// Gets first album's title
const rows = results[0].map(row => row.toJSON());
const albumTitle = rows[0].AlbumTitle;
console.log(`previous album title ${albumTitle}`);
const update =
"UPDATE Albums SET AlbumTitle = 'New Album Title' WHERE SingerId = 1 AND AlbumId = 1";
const [rowCount] = await transaction.runUpdate(update);
console.log(
`Successfully updated ${rowCount} record in Albums table.`,
);
await transaction.commit();
console.log(
'Successfully executed read-write transaction with isolationLevel option.',
);
} catch (err) {
console.error('ERROR:', err);
} finally {
transaction.end();
// Close the database when finished.
await database.close();
}
},
);
}
runTransactionWithIsolationLevel();
Python
# instance_id = "your-spanner-instance"
# database_id = "your-spanner-db-id"
# The isolation level specified at the client-level will be applied to all RW transactions.
isolation_options_for_client = TransactionOptions.IsolationLevel.SERIALIZABLE
spanner_client = spanner.Client(
default_transaction_options=DefaultTransactionOptions(
isolation_level=isolation_options_for_client
)
)
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)
# The isolation level specified at the request level takes precedence over the isolation level configured at the client level.
isolation_options_for_transaction = (
TransactionOptions.IsolationLevel.REPEATABLE_READ
)
def update_albums_with_isolation(transaction):
# Read an AlbumTitle.
results = transaction.execute_sql(
"SELECT AlbumTitle from Albums WHERE SingerId = 1 and AlbumId = 1"
)
for result in results:
print("Current Album Title: {}".format(*result))
# Update the AlbumTitle.
row_ct = transaction.execute_update(
"UPDATE Albums SET AlbumTitle = 'A New Title' WHERE SingerId = 1 and AlbumId = 1"
)
print("{} record(s) updated.".format(row_ct))
database.run_in_transaction(
update_albums_with_isolation, isolation_level=isolation_options_for_transaction
)
REST
您可以使用 TransactionOptions.isolation_level
REST API,在交易層級設定讀寫交易的隔離等級。有效選項為 TransactionOptions.SERIALIZABLE
和 TransactionOptions.REPEATABLE_READ
。根據預設,Spanner 會將隔離等級設為可序列化隔離。
RPC
不支援的用途
- 您只能在讀寫交易中使用可重複讀取隔離層級。 如果您在唯讀交易中設定這個隔離等級,Spanner 會傳回錯誤。所有唯讀交易都會在固定快照中運作,且不需要鎖定。
- 您無法使用可重複讀取隔離層級,修改在資料欄中定義全文搜尋索引的資料表。
後續步驟
進一步瞭解隔離層級。
如要進一步瞭解 Spanner 可序列化和外部一致性,請參閱「TrueTime 與外部一致性」。