동시 실행 제어

Spanner 트랜잭션은 비관적 및 낙관적 의 두 가지 동시 실행 제어 모드를 제공합니다. 동시 실행 제어 모드를 선택하면 트랜잭션이 동시 읽기 및 쓰기를 처리하는 방식에 영향을 미쳐 성능, 지연 시간, 트랜잭션 취소율에 영향을 미칩니다. 애플리케이션의 성능 및 일관성 요구사항에 가장 적합한 모드를 선택하세요.

기본 동작은 트랜잭션에서 사용하는 격리 수준 에 따라 다릅니다.

비관적 동시 실행 제어

기본적으로 Spanner는 직렬화 가능한 격리를 사용하여 비관적 동시 실행을 사용합니다. 반복 가능한 읽기 격리를 사용하여 비관적 동시 실행을 사용할 수도 있습니다.

직렬화 가능한 격리의 비관적 동시 실행

이 모드는 동시 트랜잭션이 동일한 데이터를 경합할 수 있다고 가정합니다. 트랜잭션 내에서 데이터를 읽거나 쓸 때 데이터에 잠금 을 사전에 획득합니다. 또한 트랜잭션에서 이전에 획득한 잠금이 이후 문에서 계속 유지되는지 확인합니다. Spanner가 잠금 충돌을 감지하면 wound-wait 알고리즘을 사용하여 충돌을 해결합니다.

비관적 동시 실행에서 트랜잭션은 트랜잭션의 실행 및 커밋 단계 모두에서 데이터에 대한 잠금을 획득합니다.

  • 읽기: 트랜잭션이 데이터를 읽으면 실행 단계에서 공유 읽기 (ReaderShared) 잠금 을 획득합니다. 이러한 잠금은 트랜잭션이 커밋될 때까지 유지됩니다.
  • DML 및 쓰기:
    • 실행 중에 DML 또는 쓰기로 수정된 데이터의 경우 트랜잭션이 행 존재에 대한 읽기 잠금을 획득할 수 있습니다.
    • 커밋 시점에 트랜잭션은 작성된 데이터에 대한 쓰기 또는 배타적 잠금을 획득하려고 시도합니다. 쓰기 잠금은 동시 읽기를 차단하지만 동시 쓰기를 차단하지 않을 수 있습니다. 특히 둘 다 쓰기 잠금을 사용하는 경우에 그렇습니다. 즉, 여러 트랜잭션이 커밋을 진행할 수 있으며 쓰기-쓰기 충돌은 wound-wait 알고리즘을 사용하여 커밋 시점에 해결됩니다. 모든 잠금은 트랜잭션이 커밋될 때까지 유지됩니다.

반복 가능한 읽기 격리의 비관적 동시 실행

반복 가능한 읽기 격리에서 비관적 동시 실행을 사용하여 쓰기를 직렬화합니다. 이 모드에서 읽기 작업은 스냅샷을 사용하지만 배타적 잠금 은 FOR UPDATE 쿼리 또는 lock_scanned_ranges=exclusive 힌트에서 읽은 데이터와 DML 쿼리로 작성된 데이터에 적용됩니다.

직렬화 가능한 격리를 사용한 비관적 동시 실행의 이점

직렬화 가능한 격리를 사용하여 비관적 동시 실행을 사용하는 주요 이점은 경합이 심한 워크로드에서 트랜잭션이 진행되는 데 도움이 된다는 것입니다. Spanner는 충돌 중에 새 트랜잭션보다 오래된 트랜잭션에 우선순위를 부여하여 트랜잭션이 결국 완료되도록 하면서 반복적으로 트랜잭션을 취소하는 양을 줄입니다.

반복 가능한 읽기 격리를 사용한 비관적 동시 실행의 이점

반복 가능한 읽기 격리를 사용하면 잠금을 획득하는 트랜잭션이 트랜잭션이 커밋되기 전에 FOR UPDATE가 있는 쿼리의 일부로 또는 DML 쿼리의 일부로 읽은 데이터가 동시 트랜잭션에 의해 수정된 경우 커밋 시점에 여전히 취소될 수 있습니다. 하지만 잠금이 획득된 후에는 트랜잭션이 커밋될 때까지 추가 동시 업데이트를 방지하여 쓰기를 직렬화합니다.

비관적 동시 실행의 위험

직렬화 가능한 격리를 사용한 비관적 동시 실행에는 다음과 같은 위험이 있습니다.

  • 장기 실행되는 읽기는 지연 시간에 민감한 쓰기를 차단할 수 있습니다.
  • 완료 전에 사용자 상호작용이 포함된 트랜잭션으로 인해 잠금이 장기간 유지되어 다른 작업을 차단할 수 있습니다.

직렬화 가능한 격리를 사용한 비관적 동시 실행의 사용 사례

비관적 동시 실행은 읽기-쓰기 및 쓰기-쓰기 경합이 심한 워크로드에 적합합니다. 트랜잭션 취소 및 재시도가 비용이 많이 드는 경우에도 적합합니다. 워크로드에 과도한 장기 잠금 지연이 있거나 잠금 충돌로 인해 크게 영향을 받는 경우가 아니면 이 기본 모드를 사용하세요.

반복 가능한 읽기 격리를 사용한 비관적 동시 실행의 사용 사례

FOR UPDATE 절 또는 DML 쿼리가 잠금을 획득해야 하는 워크로드에는 반복 가능한 읽기를 사용한 비관적 동시 실행을 사용하세요. 이 접근 방식은 이러한 문의 잠금을 획득하는 다른 데이터베이스에서 Spanner로 마이그레이션된 워크로드에 특히 유용합니다.

낙관적 동시 실행 제어

Spanner는 낙관적 동시 실행 제어도 제공합니다. 반복 가능한 읽기 격리를 사용하는 경우 기본 모드는 낙관적 동시 실행 제어입니다. 낙관적 동시 실행 제어를 사용하도록 직렬화 가능한 격리를 구성할 수도 있습니다.

낙관적 동시 실행 제어는 충돌이 드물다고 가정합니다. 읽기-쓰기 트랜잭션 내에서도 읽기 및 쿼리는 잠금을 획득하지 않고 진행됩니다. Spanner의 기본 직렬화 가능한 격리를 사용하면 읽기가 커밋 시점에 검증됩니다. 이렇게 하면 동시에 커밋된 다른 트랜잭션이 트랜잭션에서 이전에 읽은 데이터를 수정하지 않습니다. 반복 가능한 읽기 격리를 사용하는 경우 반복 가능한 읽기 격리를 사용하는 경우 FOR UPDATE 또는 lock_scanned_ranges=exclusive 힌트가 있는 읽기는 커밋 시점에 검증됩니다. Spanner가 충돌을 감지하면 트랜잭션을 취소합니다.

낙관적 동시 실행의 작동 방식

낙관적 동시 실행은 Spanner가 읽기, 쿼리, 트랜잭션 커밋을 실행하는 방식을 변경합니다. 읽기 단계에서 잠금 없는 실행을 수행하고 커밋 시점에 일관성을 검증합니다.

읽기 및 쿼리

읽기 및 쿼리는 잠금이 없습니다. 낙관적 트랜잭션 내의 모든 읽기 및 쿼리는 단일 스냅샷 타임스탬프에서 실행됩니다. Spanner는 첫 번째 읽기 또는 쿼리가 실행될 때 이 타임스탬프를 선택합니다. 이렇게 하면 트랜잭션 내의 모든 후속 읽기 및 쿼리가 첫 번째 읽기 또는 쿼리 전에 커밋된 쓰기를 볼 수 있습니다.

읽기 및 쓰기

읽기 및 쓰기가 있는 낙관적 트랜잭션의 경우 Spanner는 커밋 시점에 검증 단계를 수행합니다. 충돌이 감지되지 않고 다음 조건이 충족되는 경우에만 트랜잭션이 커밋됩니다.

  • 동시에 커밋된 쓰기가 이 트랜잭션에서 읽은 데이터와 충돌하지 않습니다. 즉, 읽기 타임스탬프 이후에 쓰기가 커밋되었지만 이 트랜잭션이 자체 쓰기를 커밋하기 전에는 쓰기가 커밋되지 않았습니다.
  • 읽기 타임스탬프 이후에 스키마가 수정되지 않았습니다.

격리 수준은 검증되는 읽기 집합을 결정합니다. 직렬화 가능한 격리를 사용하면 모든 읽기가 검증됩니다. 반복 가능한 읽기 격리를 사용하는 경우 FOR UPDATE 또는 lock_scanned_ranges=exclusive 힌트가 있는 읽기는 커밋 시점에 검증됩니다.

경합이 심한 경우 낙관적 트랜잭션이 반복적으로 취소될 수 있습니다. 반대로 비관적 트랜잭션은 오래된 트랜잭션이 커밋되도록 허용하고 새 트랜잭션을 재시도하여 읽기-쓰기 충돌을 해결합니다.

낙관적 동시 실행의 이점

낙관적 동시 실행은 다음과 같은 이점을 제공합니다.

  • 읽기는 잠금을 획득하지 않습니다. 낙관적 트랜잭션은 읽기에 대한 잠금을 획득하지 않으므로 장기 실행되는 읽기가 지연 시간에 민감한 쓰기를 차단하지 않습니다.
  • 읽기 전용 트랜잭션의 커밋 지연 시간 감소: 낙관적 트랜잭션 내의 모든 읽기는 동일한 스냅샷 타임스탬프를 기반으로 하므로 이러한 읽기의 실행 또는 커밋 중에 일관성을 확인할 필요가 없어 지연 시간이 크게 줄어듭니다.

낙관적 동시 실행의 위험

낙관적 동시 실행은 특히 직렬화 가능한 격리와 함께 사용할 때 읽기-쓰기 경합이 심한 경우 위험을 초래합니다. 워크로드에 직렬화 가능한 격리를 사용한 낙관적 동시 실행 제어를 사용하기 전에 이러한 위험을 이해하세요.

  • 읽기-쓰기 경합이 심한 경우 낙관적 트랜잭션의 읽기가 동시 쓰기로 인해 무효화될 수 있으므로 낙관적 트랜잭션의 취소율이 높을 수 있습니다.
  • 지속적인 경합이 심한 경우 트랜잭션이 반복적으로 취소되고 트랜잭션 부족으로 인해 커밋되지 않을 수 있습니다.

낙관적 동시 실행의 사용 사례

낙관적 동시 실행은 읽기-쓰기 경합이 낮은 트랜잭션 워크로드에 적합합니다. 직렬화 가능한 트랜잭션의 경우 트랜잭션 취소를 허용할 수 있는 워크로드에도 이점이 있습니다.

다음 워크로드에 낙관적 동시 실행을 고려하세요.

  • 장기 실행되는 트랜잭션이 있는 지연 시간에 민감하지 않은 낮은 우선순위 워크로드: 장기 실행되는 읽기 또는 쿼리로 인해 지연 시간에 민감한 쓰기가 지연될 수 있는 경우 낙관적 동시 실행을 사용합니다. 이렇게 하면 읽기 잠금으로 인한 지연을 방지할 수 있습니다. 예를 들어 연결이 느린 모바일 클라이언트의 트랜잭션 또는 많은 행 또는 넓은 범위에 대해 읽기 잠금을 보유하는 낮은 SLA 트랜잭션이 있습니다.
  • 읽기-쓰기 경합이 낮은 읽기 지연 시간에 민감한 트랜잭션 워크로드: 멀티 리전 구성에서 낙관적 동시 실행을 사용하여 리전별로 읽기를 제공하고 읽기 지연 시간을 줄이며 핫 스플릿에 대한 급증하는 읽기 트래픽으로 인한 프로덕션 문제를 방지합니다. 또한 리더 과부하 또는 사용 불가능 상태에서 읽기 가용성을 개선합니다.
  • 대부분의 트랜잭션이 읽기 전용인 트랜잭션 워크로드: 낙관적 동시 실행으로 전환하면 이러한 워크로드에서 일반적인 읽기 전용 트랜잭션의 커밋 지연 시간이 줄어듭니다. 읽기-쓰기 트랜잭션의 취소율이 높지 않도록 읽기-쓰기 경합이 낮아야 합니다.

읽기-쓰기 충돌이 자주 발생하는 지연 시간에 민감한 트랜잭션 워크로드에는 낙관적 동시 실행을 사용하지 마세요.

동시 실행 제어 구성

Spanner 클라이언트 라이브러리, REST, RPC API를 사용하여 읽기-쓰기 트랜잭션의 동시 실행 모드를 지정할 수 있습니다.

클라이언트 라이브러리

자바

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

Spanner TransactionOptions REST API는 PESSIMISTIC 또는 OPTIMISTIC 잠금 모드를 선택할 수 있는 ReadWrite 메시지 내에 ReadLockMode enum을 제공합니다.

RPC

Spanner Transactionoptions RPC API는 PESSIMISTIC 또는 OPTIMISTIC 잠금 모드를 선택할 수 있는 ReadWrite 메시지 내에 ReadLockMode enum을 제공합니다.

드라이버

Spanner 드라이버를 사용하여 연결 수준에서 연결 매개변수로 또는 트랜잭션 수준에서 SET 문 옵션으로 read_lock_mode를 설정할 수 있습니다. 각 드라이버에 대한 자세한 내용은 드라이버 개요를 참조하세요.

다음 단계