בקרת בו-זמניות

ב-Spanner יש שני מצבים של בקרת בו-זמניות בעסקאות: פסימי ואופטימי. הבחירה במצב של בקרת בו-זמניות משפיעה על האופן שבו טרנזקציות מטפלות בפעולות קריאה וכתיבה בו-זמניות, ומשפיעה על הביצועים, זמן האחזור ושיעורי ביטול הטרנזקציות. בוחרים את המצב שהכי מתאים לדרישות הביצועים והעקביות של האפליקציה.

ההתנהגות שמוגדרת כברירת מחדל תלויה ברמת הבידוד שבה נעשה שימוש בעסקה:

בקרת בו-זמניות פסימית

כברירת מחדל, Spanner משתמש במקביליות פסימית עם בידוד ניתן לסדר. אפשר גם להשתמש במקביליות פסימית עם בידוד קריאה חוזרת.

בו-זמניות פסימית בבידוד שניתן לסדר

במצב הזה, המערכת מניחה שעסקאות מקבילות עשויות להתחרות על אותם נתונים. היא מקבלת נעילות באופן יזום על נתונים בזמן שהיא קוראת או כותבת אותם בתוך טרנזקציה. הוא גם מוודא שהנעילות שהושגו מוקדם יותר בעסקה נשארות בתוקף בהמשך. כש-Spanner מזהה התנגשות נעילה, הוא משתמש באלגוריתם wound-wait כדי לפתור את ההתנגשות.

במקביליות פסימית, העסקאות מקבלות נעילות על הנתונים במהלך שלבי הביצוע והאישור של העסקה.

  • לפעולות קריאה: כשעסקה קוראת נתונים, היא מקבלת נעילת קריאה משותפת (ReaderShared) במהלך שלב הביצוע. הנעילות האלה נשמרות עד שהטרנזקציה מתבצעת.
  • ל-DML ולפעולות כתיבה:
    • במהלך ההרצה, אם נתונים משתנים על ידי DML או פעולות כתיבה, יכול להיות שהטרנזקציה תקבל נעילות קריאה על קיום השורה.
    • בזמן ביצוע השינויים, העסקה מנסה לקבל נעילות כתיבה או נעילות בלעדיות לנתונים שנכתבו. נעילות כתיבה חוסמות קריאות בו-זמניות, אבל יכול להיות שהן לא יחסמו כתיבות בו-זמניות, במיוחד אם נעילות הכתיבה משמשות את שתי הפעולות. המשמעות היא שכמה עסקאות יכולות להתקדם לשלב האישור, והתנגשויות של כתיבה-כתיבה נפתרות בזמן האישור באמצעות אלגוריתם wound-wait. כל הנעילות נשמרות עד שהעסקה מתבצעת.

בו-זמניות פסימית בבידוד קריאה חוזרת

שימוש במקביליות פסימית בבידוד של קריאה חוזרת כדי לבצע סריאליזציה של פעולות כתיבה. במצב הזה, פעולות קריאה משתמשות בתמונות מצב, אבל נעילות בלעדיות חלות על נתונים שנקראים משאילתות FOR UPDATE או מרמזים של lock_scanned_ranges=exclusive, ועל נתונים שנכתבים באמצעות שאילתות DML.

היתרונות של שימוש במקביליות פסימית עם בידוד ניתן לסריאליזציה

היתרון העיקרי בשימוש במקביליות פסימית עם בידוד ניתן לסדרות הוא שבמקרים של עומסי עבודה עם רמת סיכון גבוהה, היא עוזרת לעסקאות להתקדם. במהלך קונפליקטים, מערכת Spanner נותנת עדיפות לעסקאות ישנות יותר על פני עסקאות חדשות יותר, כדי להבטיח שהעסקאות יושלמו בסופו של דבר ולצמצם את מספר העסקאות שבוטלו שוב ושוב.

היתרונות של שימוש במקביליות פסימית עם בידוד של קריאה חוזרת

בבידוד של קריאה חוזרת, יכול להיות שעסקאות שרוכשות נעילות עדיין יבוטלו בזמן ביצוע הפעולה (commit) אם הנתונים שנקראו כחלק משאילתה עם FOR UPDATE או כחלק משאילתת DML, שונו על ידי עסקה מקבילה לפני שהעסקה מתבצעת. אבל אחרי שהנעילות מתבצעות, הן מונעות עדכונים מקבילים נוספים עד שהעסקה מתבצעת, וכך הכתיבות מתבצעות ברצף.

הסיכונים של בו-זמניות פסימית

מקביליות פסימית עם בידוד שניתן לסדר בסדרות מציגה את הסיכונים הבאים:

  • קריאות ארוכות עלולות לחסום כתיבות שרגישות לזמן האחזור.
  • עסקאות שכוללות אינטראקציה של המשתמש לפני השלמתן עלולות לגרום לנעילות להישאר בתוקף למשך זמן רב, ולחסום פעולות אחרות.

תרחישי שימוש במקביליות פסימית עם בידוד ניתן לסריאליזציה

בו-זמניות פסימית מתאימה לעומסי עבודה עם מחלוקות רבות על קריאה-כתיבה וכתיבה-כתיבה. הוא מתאים גם כשביטול של טרנזקציה וניסיון חוזר יקרים. מומלץ להשתמש במצב ברירת המחדל הזה אלא אם עומס העבודה כולל עיכובים ארוכים מדי בנעילה או מושפע באופן משמעותי מהתנגשויות נעילה.

תרחישי שימוש במקביליות פסימית עם בידוד של קריאה חוזרת

משתמשים במקביליות פסימית עם קריאה חוזרת לעומסי עבודה שנדרש בהם סעיף FOR UPDATE או שאילתות DML כדי לקבל נעילות. הגישה הזו שימושית במיוחד לעומסי עבודה שהועברו אל Spanner ממסדי נתונים אחרים שרוכשים נעילות עבור ההצהרות האלה.

בקרת בו-זמניות אופטימית

‫Spanner מספק גם בקרת בו-זמניות אופטימית. כשמשתמשים בבידוד קריאה חוזרת, מצב ברירת המחדל הוא בקרת מקביליות אופטימית. אפשר גם להגדיר בידוד שניתן לסדר בסדרות כדי להשתמש בבקרת בו-זמניות אופטימית.

בקרת בו-זמניות אופטימית מניחה שהתנגשויות הן נדירות. פעולות קריאה ושאילתות, גם בתוך טרנזקציה של קריאה וכתיבה, מתבצעות בלי לקבל נעילות. בבידוד הניתן לסדרות (serializable) שמוגדר כברירת מחדל ב-Spanner, פעולות קריאה מאומתות בזמן ביצוע הפעולה (commit). כך מוודאים שאף עסקה אחרת שבוצעה במקביל לא שינתה את הנתונים שהעסקה קראה קודם. אם משתמשים בבידוד קריאה חוזרת, קריאות עם רמז FOR UPDATE או lock_scanned_ranges=exclusive מאומתות בזמן השמירה. אם Spanner מזהה התנגשות, הוא מבטל את העסקה.

איך פועלת אופטימיזציה של פעולות בו-זמניות

בו-זמניות אופטימית משנה את האופן שבו Spanner מבצע קריאות, שאילתות ומחויבויות של טרנזקציות. הוא מבצע הרצה ללא נעילה במהלך שלב הקריאה ומאמת את העקביות בשלב השמירה.

לקריאות ולשאילתות

קריאות ושאילתות לא גורמות לנעילה. כל פעולות הקריאה והשאילתות בטרנזקציה אופטימית מבוצעות בנקודת זמן אחת של תמונת מצב. מערכת Spanner בוחרת את חותמת הזמן הזו כשהפעולה הראשונה של קריאה או שאילתה מבוצעת. כך מובטח שכל הקריאות והשאילתות הבאות בתוך העסקה יראו את הפעולות שבוצעו לפני הקריאה או השאילתה הראשונה.

לקריאה וכתיבה

בטרנזקציה אופטימית עם פעולות קריאה וכתיבה, Spanner מבצע שלב אימות בזמן השמירה. העסקה תתבצע בהצלחה רק אם לא יזוהו התנגשויות ויתקיימו התנאים הבאים:

  • אין התנגשות בין פעולות כתיבה שבוצעו בו-זמנית לבין הנתונים שנקראו על ידי העסקה הזו. כלומר, לא בוצעו פעולות כתיבה אחרי חותמת הזמן של הקריאה, אבל לפני שהעסקה הזו ביצעה את פעולות הכתיבה שלה.
  • הסכימה לא שונתה מאז חותמת הזמן של הקריאה.

רמת הבידוד קובעת את קבוצת הקריאות שעוברות אימות. בבידוד ניתן לסדר את כל הקריאות. בבידוד קריאה חוזרת, קריאות עם רמז FOR UPDATE או lock_scanned_ranges=exclusive מאומתות בזמן ביצוע השינויים.

במקרים של מחלוקת גבוהה, יכול להיות שעסקאות אופטימיות יבוטלו שוב ושוב. לעומת זאת, בעסקאות פסימיות, התנגשויות קריאה-כתיבה נפתרות על ידי אישור העסקה הישנה וניסיון חוזר של העסקה החדשה.

היתרונות של אופטימיזציה של פעולות מקבילות

היתרונות של שימוש במקביליות אופטימית:

  • פעולות קריאה לא מקבלות נעילות: עסקאות אופטימיות לא מקבלות נעילות לקריאות, כך שקריאות ארוכות לא חוסמות כתיבות רגישות לזמן האחזור.
  • זמן האחזור של פעולות commit בעסקאות לקריאה בלבד קצר יותר: כל פעולות הקריאה בעסקה אופטימית מבוססות על חותמת זמן של תמונת מצב זהה, ולכן אין צורך לאמת את העקביות במהלך הביצוע או ה-commit של פעולות הקריאה האלה, מה שמקצר משמעותית את זמן האחזור.

הסיכונים של אופטימיזציה של פעולות בו-זמניות

שימוש במקביליות אופטימית כרוך בסיכונים, במיוחד במקרים של מחלוקת גבוהה על קריאה וכתיבה כשמשתמשים בבידוד ניתן לסדר. חשוב להבין את הסיכונים האלה לפני שמשתמשים בבקרת בו-זמניות אופטימית עם בידוד ניתן לסריאליזציה עבור עומס העבודה.

  • במקרים של תחרות גבוהה על קריאה וכתיבה, יכול להיות שיהיה שיעור גבוה של ביטולים בעסקאות אופטימיות, כי פעולות כתיבה מקבילות עלולות לפסול את הקריאות של עסקה אופטימית.
  • אם יש תחרות גבוהה ומתמשכת על משאבים, יכול להיות שעסקה תבוטל שוב ושוב ולא תאושר בגלל מחסור במשאבים.

תרחישי שימוש במקביליות אופטימית

מקביליות אופטימית מתאימה לעומסי עבודה טרנזקציונליים עם מחלוקת נמוכה על קריאה וכתיבה. בנוסף, היא מועילה לעומסי עבודה שבהם אפשר לבטל עסקאות.

כדאי להשתמש במקביליות אופטימית בעומסי העבודה הבאים:

  • עומסי עבודה עם עדיפות נמוכה, שסובלים השהיות, עם טרנזקציות ארוכות: כדאי להשתמש במקביליות אופטימית אם קריאות או שאילתות ארוכות עלולות לגרום להשהיות בכתיבות שרגישות להשהיות. כך נמנעים עיכובים שנגרמים מנעילות קריאה. לדוגמה, עסקאות בלקוחות לנייד עם חיבורים איטיים, או עסקאות עם SLA נמוך שכוללות נעילות קריאה עבור שורות רבות או טווחים גדולים.
  • קריאה של עומסי עבודה טרנזקציוניים שרגישים לזמן אחזור עם תחרות נמוכה על קריאה וכתיבה: בהגדרה של כמה אזורים, משתמשים במקביליות אופטימית כדי להציג קריאות אזוריות, להפחית את זמני האחזור של הקריאה ולהימנע מבעיות בייצור כתוצאה מתנועת קריאה לא סדירה לפיצול פעיל. הוא גם משפר את זמינות הקריאה במהלך עומס יתר או חוסר זמינות של הלידר.
  • עומסי עבודה טרנזקציוניים שבהם רוב הטרנזקציות הן לקריאה בלבד: מעבר לשימוש במקביליות אופטימית מקטין את זמן האחזור של אישור הטרנזקציות הנפוצות לקריאה בלבד בעומסי העבודה האלה. כדי למנוע שיעורי ביטול גבוהים של עסקאות קריאה-כתיבה, חשוב לוודא שיש תחרות נמוכה על משאבי קריאה-כתיבה.

מומלץ להימנע משימוש במקביליות אופטימית בעומסי עבודה טרנזקציוניים שרגישים לזמן האחזור, שבהם יש התנגשויות תכופות בין פעולות קריאה וכתיבה.

הגדרת בקרת בו-זמניות

אפשר להשתמש בספריות הלקוח של Spanner, ב-REST וב-RPC API כדי לציין את מצב הבו-זמניות של טרנזקציות לקריאה ולכתיבה.

ספריות לקוח

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;
      });
}

המשך


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

‫API בארכיטקטורת REST של Spanner TransactionOptions מספק ספירה (enum) של ReadLockMode בתוך ההודעה ReadWrite שמאפשרת לבחור את מצב הנעילה PESSIMISTIC או OPTIMISTIC.

RPC

ממשק ה-API של Spanner Transactionoptions RPC מספק את סוג ה-enum‏ ReadLockMode בהודעה ReadWrite שמאפשר לבחור את מצב הנעילה PESSIMISTIC או OPTIMISTIC.

דרייברים

אפשר להשתמש בדרייברים של Spanner כדי להגדיר את read_lock_mode כפרמטר חיבור ברמת החיבור או כאפשרות של הצהרת SET ברמת העסקה. מידע נוסף על כל דרייבר זמין במאמר סקירה כללית על דרייברים.

המאמרים הבאים