Wiederholbare Leseisolation verwenden

Auf dieser Seite wird beschrieben, wie Sie die Isolation wiederholbarer Lesevorgänge in Cloud Spanner verwenden.

Die Isolation wiederholbarer Lesevorgänge ist eine Isolationsebene, die dafür sorgt, dass alle Lesevorgänge innerhalb einer Transaktion einen konsistenten Snapshot der Datenbank sehen, wie er zu Beginn der Transaktion vorlag. In Cloud Spanner wird diese Isolationsebene mit einer Technik implementiert, die auch als Snapshot-Isolation bezeichnet wird. Dieser Ansatz ist in Szenarien mit hoher Nebenläufigkeit von Lese-/Schreibvorgängen vorteilhaft, in denen zahlreiche Transaktionen Daten lesen, die von anderen Transaktionen geändert werden könnten. Durch die Verwendung eines festen Snapshots vermeidet die Isolation wiederholbarer Lesevorgänge die Leistungseinbußen der strengeren serialisierbaren Isolationsebene. Lesevorgänge können ausgeführt werden, ohne Sperren zu erwerben und ohne gleichzeitige Schreibvorgänge zu blockieren. Dies führt möglicherweise zu weniger abgebrochenen Transaktionen, die aufgrund von Serialisierungskonflikten wiederholt werden müssen. Weitere Informationen finden Sie unter Übersicht über Isolationsebenen.

Isolationsebene festlegen

Sie können die Isolationsebene für Lese-Schreib-Transaktionen auf Datenbankclient- oder Transaktionsebene mit den folgenden Methoden festlegen:

Clientbibliotheken

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";
        String title = null;
        try (ResultSet resultSet = transaction.executeQuery(Statement.of(selectSql))) {
          if (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 defaultTransactionOptions = {
  isolationLevel:
    protos.google.spanner.v1.TransactionOptions.IsolationLevel.SERIALIZABLE,
};

// Instantiates a client with defaultTransactionOptions
const spanner = new Spanner({
  projectId: projectId,
  defaultTransactionOptions,
});

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);
        transaction.end();
      } finally {
        // Close the database when finished.
        await database.close();
      }
    },
  );
}
runTransactionWithIsolationLevel();

Python

# instance_id = "your-spanner-instance"
# database_id = "your-spanner-db-id"
from google.cloud.spanner_v1 import TransactionOptions, DefaultTransactionOptions

# 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

Mit der TransactionOptions.isolation_level REST API können Sie die Isolationsebene für Lese-Schreib- und schreibgeschützte Transaktionen auf Transaktionsebene festlegen. Die gültigen Optionen sind TransactionOptions.SERIALIZABLE und TransactionOptions.REPEATABLE_READ. Standardmäßig legt Cloud Spanner die Isolationsebene auf serialisierbare Isolation fest.

Beschränkungen

In der Vorschau der Isolation wiederholbarer Lesevorgänge gelten die folgenden Einschränkungen.

  • Wenn Ihr Schema Prüfeinschränkungen enthält, können Probleme auftreten.
    • Es gibt ein bekanntes Problem, das verhindert, dass Prüfeinschränkungen validiert werden. Dies kann zu Einschränkungsverletzungen führen, wenn Transaktionen ausgeführt werden. Daher empfehlen wir, die Isolation wiederholbarer Lesevorgänge in der Vorschau nicht zu verwenden, wenn Ihr Schema Prüfeinschränkungen enthält.
  • Wenn in Ihrer Datenbank gleichzeitige Schemaänderungen auftreten, während Transaktionen ausgeführt werden, können Probleme auftreten.
    • Wenn Ihre DML-Anweisungen die last_statement Option verwenden und eine gleichzeitige Schemaänderung auftritt, während die DML-Anweisung ausgeführt wird, kann sie intern wiederholt werden und eine Fehlermeldung zurückgeben, die besagt, dass die DML-Anweisung nach dem Festlegen der last_statement Option falsch wiederholt wurde. Wenn Sie die Transaktion nach der Schemaänderung wiederholen, wird dieses Problem behoben.
    • Wenn bei Anfragen in einer Transaktion ein DEADLINE_EXCEEDED-Fehler vom Client auftritt, wiederholen Sie die Transaktion, nachdem die Schemaänderung angewendet wurde, um das Problem zu beheben.

Nicht unterstützte Anwendungsfälle

  • Sie können die Isolation wiederholbarer Lesevorgänge nicht für partitionierte DML-Transaktionen festlegen.
  • Alle schreibgeschützten Transaktionen werden bereits mit einem festen Snapshot ausgeführt und erfordern keine Sperren. Wenn Sie die Isolation wiederholbarer Lesevorgänge für diesen Transaktionstyp festlegen, ändert sich daher nichts.
  • Sie können die Isolation wiederholbarer Lesevorgänge nicht für schreibgeschützte, einmalige und partitionierte Vorgänge mit den Cloud Spanner-Clientbibliotheken festlegen. Die Cloud Spanner-Clientbibliotheken bieten keine Option zum Festlegen der Isolation wiederholbarer Lesevorgänge für schreibgeschützte, einmalige und partitionierte Abfragevorgänge.

Nächste Schritte