Gleichzeitigkeitserkennung

Spanner-Transaktionen bieten zwei Modi für die Nebenläufigkeitserkennung: pessimistisch und optimistisch. Die Wahl des Modus für die Nebenläufigkeitserkennung wirkt sich darauf aus, wie Transaktionen gleichzeitige Lese- und Schreibvorgänge verarbeiten. Dies beeinflusst die Leistung, die Latenz und die Transaktionsabbruchraten. Wählen Sie den Modus aus, der am besten zu den Leistungs- und Konsistenzanforderungen Ihrer Anwendung passt.

Das Standardverhalten hängt von der Isolationsebene ab, die Ihre Transaktion verwendet:

Pessimistische Nebenläufigkeitserkennung

Standardmäßig verwendet Spanner die pessimistische Gleichzeitigkeitserkennung mit serialisierbarer Isolation. Sie können die pessimistische Gleichzeitigkeitserkennung auch mit der Isolation wiederholbarer Lesevorgänge verwenden.

Pessimistische Gleichzeitigkeitserkennung bei serialisierbarer Isolation

In diesem Modus wird davon ausgegangen, dass gleichzeitige Transaktionen um dieselben Daten konkurrieren. Sperren werden proaktiv für Daten abgerufen, wenn sie innerhalb einer Transaktion gelesen oder geschrieben werden. Außerdem wird geprüft, ob Sperren, die früher in der Transaktion abgerufen wurden, in späteren Anweisungen beibehalten werden. Wenn Spanner einen Sperrenkonflikt erkennt, wird der Konflikt mit dem Wound-Wait-Algorithmus gelöst.

Bei der pessimistischen Nebenläufigkeit rufen Transaktionen Sperren für Daten sowohl während der Ausführungs- als auch der Commit-Phase der Transaktion ab.

  • Für Lesevorgänge:Wenn eine Transaktion Daten liest, wird während der Ausführungsphase eine gemeinsame Lesesperre (ReaderShared) abgerufen. Diese Sperren werden beibehalten, bis die Transaktion mit Commit bestätigt wird.
  • Für DML und Schreibvorgänge
    • Während der Ausführung ruft die Transaktion für Daten, die durch DML oder Schreibvorgänge geändert wurden, möglicherweise Lesesperren für die Existenz von Zeilen ab.
    • Zum Zeitpunkt des Commits versucht die Transaktion, Schreib- oder exklusive Sperren für die geschriebenen Daten abzurufen. Schreibsperren blockieren gleichzeitige Lesevorgänge, aber möglicherweise nicht gleichzeitige Schreibvorgänge, insbesondere wenn beide Schreibsperren verwenden. Das bedeutet, dass mehrere Transaktionen mit Commit bestätigt werden können und Schreib-Schreib-Konflikte zum Zeitpunkt des Commits mit dem Wound-Wait-Algorithmus gelöst werden. Alle Sperren werden beibehalten, bis die Transaktion mit Commit bestätigt wird.

Pessimistische Nebenläufigkeit bei Isolation wiederholbarer Lesevorgänge

Verwenden Sie die pessimistische Gleichzeitigkeitserkennung bei der Isolation wiederholbarer Lesevorgänge, um Schreibvorgänge zu serialisieren. In diesem Modus verwenden Lesevorgänge Snapshots, aber exklusive Sperren gelten für Daten, die aus FOR UPDATE Abfragen oder lock_scanned_ranges=exclusive Hinweisen gelesen wurden, und für Daten, die mit DML-Abfragen geschrieben wurden.

Vorteile der pessimistischen Nebenläufigkeit mit serialisierbarer Isolation

Der Hauptvorteil der pessimistischen Nebenläufigkeit mit serialisierbarer Isolation besteht darin, dass sie bei stark umkämpften Arbeitslasten dazu beiträgt, dass Transaktionen vorankommen. Spanner priorisiert bei Konflikten ältere Transaktionen gegenüber neueren. So wird sichergestellt, dass Transaktionen schließlich abgeschlossen werden, und die Anzahl der wiederholten Transaktionsabbrüche wird reduziert.

Vorteile der pessimistischen Gleichzeitigkeitserkennung mit Isolation wiederholbarer Lesevorgänge

Bei der Isolation wiederholbarer Lesevorgänge können Transaktionen, die Sperren abrufen, zum Zeitpunkt des Commits dennoch abgebrochen werden, wenn die Daten, die als Teil einer Abfrage mit FOR UPDATE oder als Teil einer DML-Abfrage gelesen wurden, von einer gleichzeitigen Transaktion geändert wurden, bevor die Transaktion mit Commit bestätigt wird. Nachdem Sperren abgerufen wurden, werden jedoch weitere gleichzeitige Aktualisierungen verhindert, bis die Transaktion mit Commit bestätigt wird. So werden die Schreibvorgänge serialisiert.

Risiken der pessimistischen Nebenläufigkeit

Pessimistische Nebenläufigkeit mit serialisierbarer Isolation birgt die folgenden Risiken:

  • Lange Lesevorgänge können latenzempfindliche Schreibvorgänge blockieren.
  • Transaktionen, die vor dem Abschluss eine Nutzerinteraktion erfordern, können dazu führen, dass Sperren lange beibehalten werden, wodurch möglicherweise andere Vorgänge blockiert werden.

Anwendungsfälle für die pessimistische Nebenläufigkeit mit serialisierbarer Isolation

Die pessimistische Gleichzeitigkeitserkennung eignet sich für Arbeitslasten mit hoher Konkurrenz bei Lese-Schreib- und Schreib-Schreib-Vorgängen. Sie ist auch geeignet, wenn Transaktionsabbrüche und -wiederholungen kostspielig sind. Verwenden Sie diesen Standardmodus, es sei denn, Ihre Arbeitslast weist übermäßig lange Sperrenverzögerungen auf oder ist erheblich von Sperrenkonflikten betroffen.

Anwendungsfälle für die pessimistische Nebenläufigkeit mit Isolation wiederholbarer Lesevorgänge

Verwenden Sie die pessimistische Nebenläufigkeit mit wiederholbarem Lesen für Arbeitslasten, für die eine FOR UPDATE-Klausel oder DML-Abfragen zum Abrufen von Sperren erforderlich sind. Dieser Ansatz ist besonders nützlich für Arbeitslasten, die aus anderen Datenbanken zu Spanner migriert wurden und für diese Anweisungen Sperren abrufen.

Optimistische Nebenläufigkeitserkennung

Spanner bietet auch eine optimistische Nebenläufigkeitserkennung. Wenn Sie die Isolation wiederholbarer Lesevorgänge verwenden, ist der Standardmodus die optimistische Gleichzeitigkeitserkennung. Sie können auch die serialisierbare Isolation so konfigurieren, dass die optimistische Nebenläufigkeitserkennung verwendet wird.

Bei der optimistischen Nebenläufigkeitserkennung wird davon ausgegangen, dass Konflikte selten sind. Lese- und Abfragevorgänge werden auch innerhalb einer Lese-Schreib-Transaktion ohne Abrufen von Sperren ausgeführt. Bei der standardmäßigen serialisierbaren Isolation von Spanner werden Lesevorgänge zum Zeitpunkt des Commits validiert. So wird sichergestellt, dass keine andere gleichzeitig mit Commit bestätigte Transaktion die zuvor von der Transaktion gelesenen Daten geändert hat. Wenn Sie die Isolation wiederholbarer Lesevorgänge, Lesevorgänge mit einem FOR UPDATE oder lock_scanned_ranges=exclusive Hinweis zum Zeitpunkt des Commits validiert werden. Wenn Spanner einen Konflikt erkennt, wird die Transaktion abgebrochen.

Funktionsweise der optimistischen Gleichzeitigkeitserkennung

Die optimistische Nebenläufigkeit ändert die Art und Weise, wie Spanner Lesevorgänge, Abfragen und Transaktions-Commits ausführt. Während der Lesevorgänge werden keine Sperren verwendet und die Konsistenz wird beim Commit validiert.

Für Lese- und Abfragevorgänge

Für Lese- und Abfragevorgänge werden keine Sperren verwendet. Alle Lese- und Abfragevorgänge innerhalb einer optimistischen Transaktion werden mit einem einzelnen Snapshot-Zeitstempel ausgeführt. Spanner wählt diesen Zeitstempel aus, wenn der erste Lese- oder Abfragevorgang ausgeführt wird. So wird sichergestellt, dass bei allen nachfolgenden Lese- und Abfragevorgängen innerhalb der Transaktion Schreibvorgänge berücksichtigt werden, die vor dem ersten Lese- oder Abfragevorgang mit Commit bestätigt wurden.

Für Lese- und Schreibvorgänge

Bei einer optimistischen Transaktion mit Lese- und Schreibvorgängen führt Spanner zum Zeitpunkt des Commits einen Validierungsschritt aus. Die Transaktion wird nur dann mit Commit bestätigt, wenn keine Konflikte erkannt werden und die folgenden Bedingungen erfüllt sind:

  • Es gibt keine gleichzeitig mit Commit bestätigten Schreibvorgänge, die mit den von dieser Transaktion gelesenen Daten in Konflikt stehen. Das heißt, es wurden keine Schreibvorgänge nach dem Zeitstempel des Lesevorgangs, aber vor dem Commit der eigenen Schreibvorgänge dieser Transaktion ausgeführt.
  • Das Schema wurde seit dem Zeitstempel des Lesevorgangs nicht geändert.

Die Isolationsebene bestimmt die Menge der Lesevorgänge, die validiert werden. Bei der serialisierbaren Isolation werden alle Lesevorgänge validiert. Bei der Isolation wiederholbarer Lesevorgänge werden Lesevorgänge mit einem FOR UPDATE- oder lock_scanned_ranges=exclusive-Hinweis zum Zeitpunkt des Commits validiert.

Bei hoher Konkurrenz können optimistische Transaktionen wiederholt abgebrochen werden. Im Gegensatz dazu werden bei pessimistischen Transaktionen Lese-Schreib-Konflikte gelöst, indem die ältere Transaktion mit Commit bestätigt und die neuere Transaktion wiederholt wird.

Vorteile der optimistischen Gleichzeitigkeitserkennung

Die optimistische Nebenläufigkeit bietet die folgenden Vorteile:

  • Für Lesevorgänge werden keine Sperren abgerufen: Bei optimistischen Transaktionen werden für Lesevorgänge keine Sperren abgerufen. Lange Lesevorgänge blockieren also keine latenzempfindlichen Schreibvorgänge.
  • Reduzierte Commit-Latenz für schreibgeschützte Transaktionen: Da alle Lesevorgänge innerhalb einer optimistischen Transaktion auf demselben Snapshot-Zeitstempel basieren, ist es nicht erforderlich, die Konsistenz während der Ausführung oder des Commits für diese Lesevorgänge zu überprüfen. Dadurch wird die Latenz erheblich reduziert.

Risiken der optimistischen Nebenläufigkeit

Die optimistische Gleichzeitigkeitserkennung birgt Risiken, insbesondere bei hoher Konkurrenz bei Lese-Schreib-Vorgängen, wenn sie mit serialisierbarer Isolation verwendet wird. Sie sollten diese Risiken kennen, bevor Sie die optimistische Nebenläufigkeitserkennung mit serialisierbarer Isolation für Ihre Arbeitslast verwenden.

  • Bei hoher Konkurrenz bei Lese-Schreib-Vorgängen kann es bei optimistischen Transaktionen zu einer hohen Abbruchrate kommen, da gleichzeitige Schreibvorgänge die Lesevorgänge einer optimistischen Transaktion ungültig machen können.
  • Bei anhaltend hoher Konkurrenz kann eine Transaktion wiederholt abgebrochen werden und aufgrund von Transaktionsmangel nie mit Commit bestätigt werden.

Anwendungsfälle für die optimistische Gleichzeitigkeitserkennung

Die optimistische Nebenläufigkeit eignet sich für transaktionale Arbeitslasten mit geringer Konkurrenz bei Lese-Schreib-Vorgängen. Bei serialisierbaren Transaktionen ist sie auch für Arbeitslasten von Vorteil, bei denen Transaktionsabbrüche toleriert werden können.

Erwägen Sie die optimistische Nebenläufigkeit für die folgenden Arbeitslasten:

  • Arbeitslasten mit niedriger Priorität und Latenztoleranz mit langen Transaktionen:Verwenden Sie die optimistische Nebenläufigkeit, wenn lange Lese- oder Abfragevorgänge latenzempfindliche Schreibvorgänge verzögern könnten. So werden Verzögerungen durch Lesesperren vermieden. Beispiele sind Transaktionen in mobilen Clients mit langsamen Verbindungen oder Transaktionen mit niedriger SLA, die Lesesperren für viele Zeilen oder große Bereiche beibehalten.
  • Transaktionale Arbeitslasten mit geringer Konkurrenz bei Lese-Schreib-Vorgängen, die latenzempfindlich sind Verwenden Sie in einer multiregionalen Konfiguration die optimistische Nebenläufigkeit, um Lesevorgänge regional auszuführen, die Leselatenzen zu reduzieren und Produktionsprobleme durch unregelmäßigen Lesetraffic zu einem Hot Split zu vermeiden. Außerdem wird die Leseverfügbarkeit bei Überlastung oder Nichtverfügbarkeit des Leaders verbessert.
  • Transaktionale Arbeitslasten, bei denen die meisten Transaktionen schreibgeschützt sind:Durch den Wechsel zur optimistischen Nebenläufigkeit wird die Commit-Latenz für häufige schreibgeschützte Transaktionen in diesen Arbeitslasten reduziert. Achten Sie auf eine geringe Konkurrenz bei Lese-Schreib-Vorgängen, um hohe Abbruchraten für Lese-Schreib-Transaktionen zu vermeiden.

Vermeiden Sie die Verwendung der optimistischen Nebenläufigkeit für latenzempfindliche transaktionale Arbeitslasten, bei denen häufig Lese-Schreib-Konflikte auftreten.

Nebenläufigkeitserkennung konfigurieren

Sie können die Spanner-Clientbibliotheken, die REST API und die RPC API verwenden, um den Modus für die Nebenläufigkeit für Lese-Schreib-Transaktionen anzugeben.

Clientbibliotheken

Java

static void readLockModeSetting(DatabaseId db) {
  // The read lock mode specified at the client-level will be applied to all
  // RW transactions.
  DefaultReadWriteTransactionOptions transactionOptions =
      DefaultReadWriteTransactionOptions.newBuilder()
          .setReadLockMode(ReadLockMode.OPTIMISTIC)
          .build();
  SpannerOptions options =
      SpannerOptions.newBuilder()
          .setDefaultTransactionOptions(transactionOptions)
          .build();
  Spanner spanner = options.getService();
  DatabaseClient dbClient = spanner.getDatabaseClient(db);
  dbClient
      // The read lock mode specified at the transaction-level takes precedence
      // over the read lock mode configured at the client-level.
      .readWriteTransaction(Options.readLockMode(ReadLockMode.PESSIMISTIC))
      .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;
      });
}

Go


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
	pb "cloud.google.com/go/spanner/apiv1/spannerpb"
)

// writeWithTransactionUsingReadLockMode sets the ReadLockMode globally
// by using ClientConfig and shows how to override it for a specific
// transaction. ReadLockMode determines the locking strategy used during
// transaction execution.
func writeWithTransactionUsingReadLockMode(w io.Writer, db string) error {
	ctx := context.Background()

	// Client-level configuration: Applies to all read-write transactions
	// for this client. OPTIMISTIC mode avoids locks during reads and
	// verifies changes during the commit phase.
	cfg := spanner.ClientConfig{
		TransactionOptions: spanner.TransactionOptions{
			ReadLockMode: pb.TransactionOptions_ReadWrite_OPTIMISTIC,
		},
	}
	client, err := spanner.NewClientWithConfig(ctx, db, cfg)
	if err != nil {
		return fmt.Errorf("failed to create client: %w", err)
	}
	defer client.Close()

	// Transaction-level options take precedence over client-level
	// configuration. PESSIMISTIC mode is used here to override the
	// client-level setting and ensure immediate locking during reads.
	txnOpts := spanner.TransactionOptions{
		ReadLockMode: pb.TransactionOptions_ReadWrite_PESSIMISTIC,
	}

	_, err = client.ReadWriteTransactionWithOptions(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
		// In PESSIMISTIC mode with SERIALIZABLE isolation, the transaction
		// acquires a shared lock during this read.
		key := spanner.Key{1, 2}
		row, err := txn.ReadRow(ctx, "Albums", key, []string{"AlbumTitle"})
		if err != nil {
			return fmt.Errorf("failed to read album: %w", err)
		}
		var title string
		if err := row.Column(0, &title); err != nil {
			return fmt.Errorf("failed to get album title: %w", 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":    2,
				"AlbumTitle": "New Album Title",
			},
		}
		count, err := txn.Update(ctx, stmt)
		if err != nil {
			return fmt.Errorf("failed to update album: %w", err)
		}
		fmt.Fprintf(w, "Updated %d record(s).\n", count)
		return nil
	}, txnOpts)

	if err != nil {
		return fmt.Errorf("transaction failed: %w", err)
	}
	return nil
}

Node.js

// Imports the Google Cloud Spanner client library
const {Spanner, protos} = require('@google-cloud/spanner');
// The read lock mode specified at the client-level will be applied
// to all RW transactions.
const defaultTransactionOptions = {
  readLockMode:
    protos.google.spanner.v1.TransactionOptions.ReadWrite.ReadLockMode
      .OPTIMISTIC,
};

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

function runTransactionWithReadLockMode() {
  // Gets a reference to a Cloud Spanner instance and database
  const instance = spanner.instance(instanceId);
  const database = instance.database(databaseId);
  // The read lock mode specified at the request-level takes precedence over
  // the read lock mode configured at the client-level.
  const readLockModeOptionsForTransaction = {
    readLockMode:
      protos.google.spanner.v1.TransactionOptions.ReadWrite.ReadLockMode
        .PESSIMISTIC,
  };

  database.runTransaction(
    readLockModeOptionsForTransaction,
    async (err, transaction) => {
      if (err) {
        console.error(err);
        return;
      }
      try {
        const query =
          'SELECT AlbumTitle FROM Albums WHERE SingerId = 2 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 = 2 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 readLockMode option.',
        );
      } catch (err) {
        console.error('ERROR:', err);
        transaction.end();
      } finally {
        // Close the database when finished.
        await database.close();
      }
    },
  );
}
runTransactionWithReadLockMode();

Python

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

# The read lock mode specified at the client-level will be applied to all
# RW transactions.
read_lock_mode_options_for_client = TransactionOptions.ReadWrite.ReadLockMode.OPTIMISTIC

# Create a client that uses Serializable isolation (default) with
# optimistic locking for read-write transactions.
spanner_client = spanner.Client(
    default_transaction_options=DefaultTransactionOptions(
        read_lock_mode=read_lock_mode_options_for_client
    )
)
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)

# The read lock mode specified at the request level takes precedence over
# the read lock mode configured at the client level.
read_lock_mode_options_for_transaction = (
    TransactionOptions.ReadWrite.ReadLockMode.PESSIMISTIC
)

def update_albums_with_read_lock_mode(transaction):
    # Read an AlbumTitle.
    results = transaction.execute_sql(
        "SELECT AlbumTitle from Albums WHERE SingerId = 2 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 = 2 and AlbumId = 1"
    )

    print("{} record(s) updated.".format(row_ct))

database.run_in_transaction(
    update_albums_with_read_lock_mode,
    read_lock_mode=read_lock_mode_options_for_transaction
)

C#


using Google.Cloud.Spanner.Data;
using System;
using System.Threading;
using System.Threading.Tasks;

public class ReadLockModeAsyncSample
{
    public async Task ReadLockModeAsync(string projectId, string instanceId, string databaseId)
    {
        // Create client with ReadLockMode.Optimistic.
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId};ReadLockMode=Optimistic";

        using var connection = new SpannerConnection(connectionString);
        await connection.OpenAsync();

        // Create transaction options with ReadLockMode.Pessimistic.
        var transactionOptions = SpannerTransactionCreationOptions.ReadWrite
            .WithReadLockMode(ReadLockMode.Pessimistic);

        using var transaction = await connection.BeginTransactionAsync(transactionOptions, null, CancellationToken.None);

        var cmd = connection.CreateSelectCommand("SELECT AlbumTitle FROM Albums WHERE SingerId = 2 AND AlbumId = 1");
        cmd.Transaction = transaction;
        using (var reader = await cmd.ExecuteReaderAsync())
        {
            while (await reader.ReadAsync())
            {
                Console.WriteLine($"AlbumTitle: {reader.GetFieldValue<string>("AlbumTitle")}");
            }
        }

        var updateCmd = connection.CreateDmlCommand("UPDATE Albums SET AlbumTitle = 'A New Title' WHERE SingerId = 2 AND AlbumId = 1");
        updateCmd.Transaction = transaction;
        var rowCount = await updateCmd.ExecuteNonQueryAsync();
        Console.WriteLine($"{rowCount} records updated.");

        await transaction.CommitAsync();
    }
}

C++

void ReadLockModeSetting(std::string const& project_id,
                         std::string const& instance_id,
                         std::string const& database_id) {
  namespace spanner = ::google::cloud::spanner;
  using ::google::cloud::Options;
  using ::google::cloud::StatusOr;

  auto db = spanner::Database(project_id, instance_id, database_id);

  // The read lock mode specified at the client-level will be applied
  // to all RW transactions.
  auto options = Options{}.set<spanner::TransactionReadLockModeOption>(
      spanner::Transaction::ReadLockMode::kOptimistic);
  auto client = spanner::Client(spanner::MakeConnection(db, options));

  auto commit = client.Commit(
      [&client](
          spanner::Transaction const& txn) -> StatusOr<spanner::Mutations> {
        // Read an AlbumTitle.
        auto sql = spanner::SqlStatement(
            "SELECT AlbumTitle from Albums WHERE SingerId = @SingerId and "
            "AlbumId = @AlbumId",
            {{"SingerId", spanner::Value(2)}, {"AlbumId", spanner::Value(1)}});
        auto rows = client.ExecuteQuery(txn, std::move(sql));
        for (auto const& row :
             spanner::StreamOf<std::tuple<std::string>>(rows)) {
          if (!row) return row.status();
          std::cout << "Current Album Title: " << std::get<0>(*row) << "\n";
        }

        // Update the title.
        auto update_sql = spanner::SqlStatement(
            "UPDATE Albums "
            "SET AlbumTitle = @AlbumTitle "
            "WHERE SingerId = @SingerId and AlbumId = @AlbumId",
            {{"AlbumTitle", spanner::Value("A New Title")},
             {"SingerId", spanner::Value(2)},
             {"AlbumId", spanner::Value(1)}});
        auto result = client.ExecuteDml(txn, std::move(update_sql));
        if (!result) return result.status();
        std::cout << result->RowsModified() << " record(s) updated.\n";

        return spanner::Mutations{};
      },
      // The read lock mode specified at the transaction-level takes
      // precedence over the read lock mode configured at the client-level.
      // kPessimistic is used here to demonstrate overriding the client-level
      // setting.
      Options{}.set<spanner::TransactionReadLockModeOption>(
          spanner::Transaction::ReadLockMode::kPessimistic));

  if (!commit) throw std::move(commit).status();
  std::cout << "Update was successful [spanner_read_lock_mode]\n";
}

PHP

use Google\Cloud\Spanner\SpannerClient;
use Google\Cloud\Spanner\Transaction;
use Google\Cloud\Spanner\V1\TransactionOptions\ReadWrite\ReadLockMode;

/**
 * Shows how to run a Read Write transaction with read lock mode options.
 *
 * Example:
 * ```
 * read_lock_mode($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function read_lock_mode(string $instanceId, string $databaseId): void
{
    // The read lock mode specified at the client-level will be applied to all
    // RW transactions.
    $spanner = new SpannerClient([
        'readLockMode' => ReadLockMode::OPTIMISTIC
    ]);
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    // The read lock mode specified at the request level takes precedence over
    // the read lock mode configured at the client level.
    $database->runTransaction(function (Transaction $t) {
        // Read an AlbumTitle.
        $results = $t->execute('SELECT AlbumTitle from Albums WHERE SingerId = 2 and AlbumId = 1');
        foreach ($results as $row) {
            printf('Current Album Title: %s' . PHP_EOL, $row['AlbumTitle']);
        }

        // Update the AlbumTitle.
        $rowCount = $t->executeUpdate('UPDATE Albums SET AlbumTitle = \'A New Title\' WHERE SingerId = 2 and AlbumId = 1');

        // Commit the transaction!
        $t->commit();

        printf('%d record(s) updated.' . PHP_EOL, $rowCount);
    }, [
        'transactionOptions' => [
            'readLockMode' => ReadLockMode::PESSIMISTIC
        ]
    ]);
}

Ruby

require "google/cloud/spanner"

def spanner_read_lock_mode project_id:, instance_id:, database_id:
  # Instantiates a client with read_lock_mode: :OPTIMISTIC
  spanner = Google::Cloud::Spanner.new project: project_id
  client = spanner.client instance_id, database_id, read_lock_mode: :OPTIMISTIC

  # Overrides read_lock_mode to :PESSIMISTIC at transaction level
  client.transaction read_lock_mode: :PESSIMISTIC do |tx|
    results = tx.execute_query "SELECT AlbumTitle FROM Albums WHERE SingerId = 2 AND AlbumId = 1"

    results.rows.each do |row|
      puts "AlbumTitle: #{row[:AlbumTitle]}"
    end

    row_count = tx.execute_update "UPDATE Albums SET AlbumTitle = 'A New Title' WHERE SingerId = 2 AND AlbumId = 1"

    puts "#{row_count} records updated."
  end
end

REST

Die Spanner TransactionOptions REST API bietet eine ReadLockMode Enumeration in der ReadWrite Nachricht, mit der Sie entweder den Sperrenmodus PESSIMISTIC oder OPTIMISTIC auswählen können.

RPC

Die Spanner Transactionoptions RPC API bietet eine ReadLockMode Enumeration in der ReadWrite Nachricht, mit der Sie entweder den Sperrenmodus PESSIMISTIC oder OPTIMISTIC auswählen können.

Erfolgsfaktoren

Mit den Spanner-Treibern können Sie read_lock_mode auf Verbindungsebene als Verbindungsparameter oder auf Transaktionsebene als SET-Anweisungsoption festlegen. Weitere Informationen zu den einzelnen Treibern finden Sie unter Übersicht über Treiber.

Nächste Schritte