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, und beeinflusst Leistung, Latenz und 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 übertragen wird.
  • Für DML- und Schreibvorgänge:
    • Während der Ausführung kann die Transaktion für Daten, die durch DML oder Schreibvorgänge geändert wurden, Lesesperren für die Existenz von Zeilen abrufen.
    • 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 übertragen 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 übertragen 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 werden für Lesevorgänge Snapshots verwendet. Es gelten jedoch exklusive Sperren 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 Gleichzeitigkeitserkennung 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 weiterverarbeitet werden. 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, vor dem Commit der Transaktion von einer gleichzeitigen Transaktion geändert wurden. Nachdem Sperren abgerufen wurden, werden jedoch weitere gleichzeitige Aktualisierungen verhindert, bis die Transaktion mit Commit übertragen wird, wodurch die Schreibvorgänge serialisiert werden.

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 Lese-Schreib- und Schreib-Schreib-Konkurrenz. 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 Gleichzeitigkeitserkennung mit wiederholbaren Lesevorgängen 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 übertragene 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 Gleichzeitigkeitserkennung ändert die Art und Weise, wie Spanner Transaktionen liest, abfragt und mit Commit überträgt. Während der Lesevorgänge wird die Ausführung ohne Sperren durchgeführt und die Konsistenz wird zum Zeitpunkt des Commits validiert.

Für Lese- und Abfragevorgänge

Lese- und Abfragevorgänge werden ohne Sperren ausgeführt. 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 übertragen 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 erfolgreich mit Commit übertragen, wenn keine Konflikte erkannt werden und die folgenden Bedingungen erfüllt sind:

  • Keine gleichzeitig mit Commit übertragenen Schreibvorgänge stehen im Konflikt mit den von dieser Transaktion gelesenen Daten. Das heißt, es wurden keine Schreibvorgänge nach dem Zeitstempel des Lesevorgangs, aber vor dem Commit der eigenen Schreibvorgänge dieser Transaktion mit Commit übertragen.
  • 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 übertragen und die neuere Transaktion wiederholt wird.

Vorteile der optimistischen Gleichzeitigkeitserkennung

Die optimistische Gleichzeitigkeitserkennung bietet die folgenden Vorteile:

  • Für Lesevorgänge werden keine Sperren abgerufen: Bei optimistischen Transaktionen werden für Lesevorgänge keine Sperren abgerufen, sodass lange Lesevorgänge latenzempfindliche Schreibvorgänge nicht blockieren.
  • 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 Nebenläufigkeit birgt Risiken, insbesondere bei hoher Lese-Schreib-Konkurrenz, wenn sie mit serialisierbarer Isolation verwendet wird. Machen Sie sich mit diesen Risiken vertraut, bevor Sie die optimistische Nebenläufigkeitserkennung mit serialisierbarer Isolation für Ihre Arbeitslast verwenden.

  • Bei hoher Lese-Schreib-Konkurrenz 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 übertragen werden.

Anwendungsfälle für die optimistische Gleichzeitigkeitserkennung

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

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

  • Arbeitslasten mit niedriger Priorität und hoher Latenz mit langen Transaktionen:Verwenden Sie die optimistische Nebenläufigkeit, wenn lange Lese- oder Abfragevorgänge latenzempfindliche Schreibvorgänge verzögern können. 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 Lese-Schreib-Konkurrenz, 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 Lese-Schreib-Konkurrenz, 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 Gleichzeitigkeitserkennung 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;
      });
}

Los


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 Enum 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 Enum 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