Transaktionen

Einführung

Bei einer Transaktion in Cloud Spanner handelt es sich um eine Gruppe von Lese- und Schreibvorgängen, die zu einem einzigen logischen Zeitpunkt über Spalten, Zeilen und Tabellen in einer Datenbank atomar ausgeführt wird.

Cloud Spanner unterstützt diese Transaktionsmodi:

  • Lese-/Schreibtransaktionen sperren. Dies ist der einzige Transaktionstyp, der das Schreiben von Daten in Cloud Spanner unterstützt. Diese Transaktionen basieren auf einer pessimistischen Sperrung und, falls notwendig, einem zweiphasigen Commit. Beim Sperren können Lese-Schreib-Transaktionen abgebrochen werden. In diesem Fall muss die Anwendung den Vorgang wiederholen.

  • Schreibgeschützt. Dieser Transaktionstyp bietet garantierte Konsistenz über mehrere Lesevorgänge, unterstützt jedoch keine Schreibvorgänge. Schreibgeschützte Transaktionen können so konfiguriert werden, dass sie Lesevorgänge zu vergangenen Zeitstempeln ausführen. Schreibgeschützte Transaktionen müssen nicht per Commit übertragen werden und können nicht gesperrt werden.

  • Partitionierte DML: Dieser Transaktionstyp führt eine DML-Anweisung (Datenbearbeitungssprache) als partitionierte DML aus. Die partitionierte DML ist für Bulk-Aktualisierungen und -Löschvorgänge vorgesehen, insbesondere zum regelmäßigen Bereinigen und Backfilling.

Auf dieser Seite werden die allgemeinen Attribute und die Semantik von Transaktionen in Cloud Spanner beschrieben. Außerdem werden die nicht schreibgeschützten, schreibgeschützten und partitionierten DML-Transaktionsschnittstellen in Cloud Spanner vorgestellt.

Lese-Schreib-Transaktionen

In den folgenden Szenarien sollten Sie Lese-Schreib-Transaktionen sperren:

  • Wenn Sie einen Schreibvorgang ausführen, der vom Ergebnis eines oder mehrerer Lesevorgänge abhängt, sollten Sie diesen Schreibvorgang und die Lesevorgänge in derselben Lese-Schreib-Transaktion ausführen.
    • Beispiel: Verdoppeln Sie den Kontostand des Bankkontos A. Das Lesen des Kontostands von A sollte in der gleichen Transaktion wie der Schreibvorgang erfolgen, um den Kontostand durch den verdoppelten Wert zu ersetzen.

  • Wenn Sie einen oder mehrere Schreibvorgänge ausführen, an denen ein atomarer Commit erforderlich ist, sollten Sie diese Schreibvorgänge in derselben Lese-Schreib-Transaktion ausführen.
    • Beispiel: Sie überweisen 200 $ von Konto A zu Konto B. Beide Schreibvorgänge (einer, um A um 200 $ zu verringern, und einer, um B um 200 $ zu erhöhen) und das Lesen der ursprünglichen Kontostände sollten in derselben Transaktion stattfinden.

  • Wenn Sie einen oder mehrere Schreibvorgänge ausführen könnten, je nachdem, wie die Ergebnisse eines oder mehrerer Lesevorgänge ausfallen, sollten Sie diese Schreib- und Lesevorgänge in derselben Lese-Schreib-Transaktion ausführen, auch wenn die Schreibvorgänge letztendlich nicht ausgeführt werden.
    • Beispiel: Sie überweisen 200 $ vom Bankkonto A zu Bankkonto B, wenn der aktuelle Kontostand von A mehr als 500 $ beträgt. Ihre Transaktion sollte einen Lesevorgang des Kontostands von A und eine bedingte Anweisung enthalten, die die Schreibvorgänge enthält.

Im Folgenden wird ein Szenario beschrieben, in dem Sie keine sperrende Lese-Schreib-Transaktion verwenden sollten:

  • Wenn Sie nur Lesevorgänge ausführen und Ihren Lesevorgang mithilfe einer einzelnen Lesemethode ausdrücken können, sollten Sie diese einzelne Lesemethode oder eine schreibgeschützte Transaktion verwenden. Einzelne Lesevorgänge können im Gegensatz zu Lese-Schreib-Transaktionen nicht gesperrt werden.

Attribute

Eine Lese-Schreib-Transaktion in Cloud Spanner führt eine Reihe von Lese- und Schreibvorgängen atomisch zu einem einzelnen logischen Zeitpunkt aus. Darüber hinaus entspricht die durch den Zeitstempel angegebene Zeit, zu der Lese-Schreib-Transaktionen ausgeführt werden, der Ortszeit, wobei die Reihenfolge der Serialisierung der Reihenfolge der Zeitstempel entspricht.

Warum sollten Sie eine Lese-Schreib-Transaktion verwenden? Lese-Schreib-Transaktionen bieten die ACID-Attribute von relationalen Datenbanken (tatsächlich bieten Lese-Schreib-Transaktionen in Cloud Spanner noch stärkere Garantien als herkömmliche ACID-Transaktionen, siehe Abschnitt Semantik weiter unten).

Isolation

Transaktionen, die Lese- und Schreibvorgänge ausführen

Hier finden Sie die Isolationsattribute, die Sie für eine Lese-Schreib-Transaktion mit einer Reihe von Lese- und Schreibvorgängen erhalten:

  • Alle Lesevorgänge innerhalb dieser Transaktion geben Daten aus demselben Zeitstempel zurück.
  • Wenn eine Transaktion erfolgreich übergeben wurde, hat kein anderer Autor die bei der Transaktion gelesenen Daten nach dem Lesevorgang modifiziert.
  • Diese Attribute gelten auch für Lesevorgänge, die keine Zeilen zurückgegeben haben, und für die Lücken zwischen den Zeilen, die von Bereichslesevorgängen zurückgegeben werden: das Nicht-Vorhandensein einer Zeile zählt als Datenelement.
  • An allen Schreibvorgängen innerhalb dieser Transaktion werden zum gleichen Zeitstempel Commits durchgeführt.
  • Alle Schreibvorgänge innerhalb dieser Transaktion sind erst sichtbar, wenn der Commit der Transaktion durchgeführt wurde.

Der Effekt besteht darin, dass alle Lese- und Schreibvorgänge zu einem bestimmten Zeitpunkt stattgefunden haben, sowohl aus der Sicht der Transaktion selbst als auch aus der Sicht anderer Leser und Autoren in der Cloud Spanner-Datenbank. Anders ausgedrückt: Die Lese- und Schreibvorgänge werden letztendlich zum selben Zeitpunkt ausgeführt (eine Illustration dazu finden Sie im Abschnitt Serialisierbarkeit und externe Konsistenz weiter unten).

Transaktionen, die nur Lesevorgänge ausführen

Die Garantien für eine Lese-Schreib-Transaktion, die nur Lesevorgänge ausführt, sind ähnlich: Alle Lesevorgänge innerhalb dieser Transaktion liefern Daten aus dem gleichen Zeitstempel, auch für Zeilen, die nicht vorhanden sind. Ein Unterschied besteht darin, dass es, wenn Sie Ihre Daten lesen und später einen Commit an der Lese-Schreib-Transaktion ausführen, keine Garantie gibt, dass die Daten innerhalb der Datenbank nach dem Lesen und vor dem Commit modifiziert wurden. Wenn Sie wissen möchten, ob die Daten seit dem letzten Lesevorgang geändert wurden, sollten Sie am besten noch einen Lesevorgang ausführen (entweder in einer Lese-Schreib-Transaktion oder mit einem starken Lesevorgang). Für mehr Effizienz sollten Sie außerdem eine schreibgeschützte Transaktion anstelle einer Lese-Schreib-Transaktion verwenden, wenn Sie bereits im Vorhinein wissen, dass Sie nur Lese- und keine Schreibvorgänge ausführen werden.

Atomarität, Konsistenz, Langlebigkeit

Zusätzlich zum Isolationsattribut bietet Cloud Spanner Atomarität (falls ein Commit an einem der Schreibvorgänge in der Transaktion durchgeführt wird, werden Commits an allen durchgeführt), Konsistenz (die Datenbank bleibt nach Abschluss der Transaktion in einem konsistenten Zustand) und Langlebigkeit (übergebene Daten bleiben übergeben).

Vorteile dieser Attribute

Aufgrund dieser Attribute können Sie sich als Anwendungsentwickler auf die Genauigkeit jeder einzelnen Transaktion konzentrieren, ohne sich um den Schutz der ausgeführten Transaktion vor anderen Transaktionen, die möglicherweise zur gleichen Zeit ausgeführt werden könnten, kümmern zu müssen.

Schnittstelle

Cloud Spanner bietet eine Schnittstelle zum Ausführen eines Arbeitsablaufs im Kontext einer Lese-Schreib-Transaktion mit Wiederholungen für Transaktionsabbrüche. Dieser Punkt lässt sich mit ein wenig Kontext erläutern: Eine Cloud Spanner-Transaktion muss möglicherweise mehrmals getestet werden, bevor sie übergeben werden kann. Wenn beispielsweise zwei Transaktionen versuchen, gleichzeitig mit Daten in einer Weise zu arbeiten, die zu einer Systemsperre führen könnte, bricht Cloud Spanner eine davon ab, damit die andere Transaktion weiterarbeiten kann. (In seltenen Fällen können vorübergehende Ereignisse in Cloud Spanner dazu führen, dass einige Transaktionen abgebrochen werden.) Da Transaktionen atomar sind, hat eine abgebrochene Transaktion keine Auswirkung auf die Datenbank. Daher sollten Transaktionen so ausgeführt werden, dass sie so lange wiederholt werden, bis sie erfolgreich sind.

Wenn Sie eine Transaktion in der Cloud Spanner API verwenden, definieren Sie den Text einer Transaktion (d. h. die Lese- und Schreibvorgänge, die für eine oder mehrere Tabellen in einer Datenbank ausgeführt werden müssen) in Form eines Funktionsobjekts. Intern führt Cloud Spanner die Funktion wiederholt aus, bis die Transaktion übergeben wird oder ein nicht wiederholbarer Fehler festgestellt wird.

Beispiel

Angenommen, Sie haben auf der Seite "Schema und Datenmodell" der Tabelle Albums die Spalte MarketingBudget hinzugefügt:

CREATE TABLE Albums (
  SingerId        INT64 NOT NULL,
  AlbumId         INT64 NOT NULL,
  AlbumTitle      STRING(MAX),
  MarketingBudget INT64
) PRIMARY KEY (SingerId, AlbumId);

Ihre Marketingabteilung bittet Sie, einen Teil des Marketingbudgets von einem Album auf ein anderes Album zu übertragen, sofern die vorhandenen Budgets den Kriterien entsprechen. Sie sollten für diesen Vorgang eine sperrende Lese-Schreib-Transaktion verwenden, da die Transaktion je nach Ergebnis eines Lesevorgangs Schreibvorgänge ausführen könnte.

Das folgende Beispiel zeigt, wie Sie eine Lese-Schreib-Transaktion ausführen:

C#

public static async Task ReadWriteWithTransactionAsync(
    string projectId,
    string instanceId,
    string databaseId)
{
    // This sample transfers 200,000 from the MarketingBudget
    // field of the second Album to the first Album. Make sure to run
    // the addColumn and writeDataToNewColumn samples first,
    // in that order.

    string connectionString =
    $"Data Source=projects/{projectId}/instances/{instanceId}"
    + $"/databases/{databaseId}";

    using (TransactionScope scope = new TransactionScope(
        TransactionScopeAsyncFlowOption.Enabled))
    {
        decimal transferAmount = 200000;
        decimal secondBudget = 0;
        decimal firstBudget = 0;

        // Create connection to Cloud Spanner.
        using (var connection =
            new SpannerConnection(connectionString))
        {
            // Create statement to select the second album's data.
            var cmdLookup = connection.CreateSelectCommand(
            "SELECT * FROM Albums WHERE SingerId = 2 AND AlbumId = 2");
            // Excecute the select query.
            using (var reader = await cmdLookup.ExecuteReaderAsync())
            {
                while (await reader.ReadAsync())
                {
                    // Read the second album's budget.
                    secondBudget =
                      reader.GetFieldValue<decimal>("MarketingBudget");
                    // Confirm second Album's budget is sufficient and
                    // if not raise an exception. Raising an exception
                    // will automatically roll back the transaction.
                    if (secondBudget < transferAmount)
                    {
                        throw new Exception("The second album's "
                            + $"budget {secondBudget} "
                            + "is less than the "
                            + "amount to transfer.");
                    }
                }
            }
            // Read the first album's budget.
            cmdLookup = connection.CreateSelectCommand(
            "SELECT * FROM Albums WHERE SingerId = 1 and AlbumId = 1");
            using (var reader = await cmdLookup.ExecuteReaderAsync())
            {
                while (await reader.ReadAsync())
                {
                    firstBudget =
                      reader.GetFieldValue<decimal>("MarketingBudget");
                }
            }

            // Specify update command parameters.
            var cmd = connection.CreateUpdateCommand("Albums",
                new SpannerParameterCollection {
                {"SingerId", SpannerDbType.Int64},
                {"AlbumId", SpannerDbType.Int64},
                {"MarketingBudget", SpannerDbType.Int64},
            });
            // Update second album to remove the transfer amount.
            secondBudget -= transferAmount;
            cmd.Parameters["SingerId"].Value = 2;
            cmd.Parameters["AlbumId"].Value = 2;
            cmd.Parameters["MarketingBudget"].Value = secondBudget;
            await cmd.ExecuteNonQueryAsync();
            // Update first album to add the transfer amount.
            firstBudget += transferAmount;
            cmd.Parameters["SingerId"].Value = 1;
            cmd.Parameters["AlbumId"].Value = 1;
            cmd.Parameters["MarketingBudget"].Value = firstBudget;
            await cmd.ExecuteNonQueryAsync();
            scope.Complete();
            Console.WriteLine("Transaction complete.");
        }
    }
}

Go

func writeWithTransaction(ctx context.Context, w io.Writer, client *spanner.Client) error {
	_, err := client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
		getBudget := func(key spanner.Key) (int64, error) {
			row, err := txn.ReadRow(ctx, "Albums", key, []string{"MarketingBudget"})
			if err != nil {
				return 0, err
			}
			var budget int64
			if err := row.Column(0, &budget); err != nil {
				return 0, err
			}
			return budget, nil
		}
		album2Budget, err := getBudget(spanner.Key{2, 2})
		if err != nil {
			return err
		}
		const transferAmt = 200000
		if album2Budget >= transferAmt {
			album1Budget, err := getBudget(spanner.Key{1, 1})
			if err != nil {
				return err
			}
			album1Budget += transferAmt
			album2Budget -= transferAmt
			cols := []string{"SingerId", "AlbumId", "MarketingBudget"}
			txn.BufferWrite([]*spanner.Mutation{
				spanner.Update("Albums", cols, []interface{}{1, 1, album1Budget}),
				spanner.Update("Albums", cols, []interface{}{2, 2, album2Budget}),
			})
			fmt.Fprintf(w, "Moved %d from Album2's MarketingBudget to Album1's.", transferAmt)
		}
		return nil
	})
	return err
}

Java

static void writeWithTransaction(DatabaseClient dbClient) {
  dbClient
      .readWriteTransaction()
      .run(
          new TransactionCallable<Void>() {
            @Override
            public Void run(TransactionContext transaction) throws Exception {
              // Transfer marketing budget from one album to another. We do it in a transaction to
              // ensure that the transfer is atomic.
              Struct row =
                  transaction.readRow("Albums", Key.of(2, 2), Arrays.asList("MarketingBudget"));
              long album2Budget = row.getLong(0);
              // Transaction will only be committed if this condition still holds at the time of
              // commit. Otherwise it will be aborted and the callable will be rerun by the
              // client library.
              long transfer = 200000;
              if (album2Budget >= transfer) {
                long album1Budget =
                    transaction
                        .readRow("Albums", Key.of(1, 1), Arrays.asList("MarketingBudget"))
                        .getLong(0);
                album1Budget += transfer;
                album2Budget -= transfer;
                transaction.buffer(
                    Mutation.newUpdateBuilder("Albums")
                        .set("SingerId")
                        .to(1)
                        .set("AlbumId")
                        .to(1)
                        .set("MarketingBudget")
                        .to(album1Budget)
                        .build());
                transaction.buffer(
                    Mutation.newUpdateBuilder("Albums")
                        .set("SingerId")
                        .to(2)
                        .set("AlbumId")
                        .to(2)
                        .set("MarketingBudget")
                        .to(album2Budget)
                        .build());
              }
              return null;
            }
          });
}

Node.js

// This sample transfers 200,000 from the MarketingBudget field
// of the second Album to the first Album, as long as the second
// Album has enough money in its budget. Make sure to run the
// addColumn and updateData samples first (in that order).

// Imports the Google Cloud client library
const {Spanner} = require('@google-cloud/spanner');

/**
 * TODO(developer): Uncomment the following lines before running the sample.
 */
// const projectId = 'my-project-id';
// const instanceId = 'my-instance';
// const databaseId = 'my-database';

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

// Gets a reference to a Cloud Spanner instance and database
const instance = spanner.instance(instanceId);
const database = instance.database(databaseId);

const transferAmount = 200000;

database.runTransaction(async (err, transaction) => {
  if (err) {
    console.error(err);
    return;
  }
  let firstBudget, secondBudget;
  const queryOne = {
    columns: [`MarketingBudget`],
    keys: [[2, 2]], // SingerId: 2, AlbumId: 2
  };

  const queryTwo = {
    columns: ['MarketingBudget'],
    keys: [[1, 1]], // SingerId: 1, AlbumId: 1
  };

  Promise.all([
    // Reads the second album's budget
    transaction.read('Albums', queryOne).then(results => {
      // Gets second album's budget
      const rows = results[0].map(row => row.toJSON());
      secondBudget = rows[0].MarketingBudget;
      console.log(`The second album's marketing budget: ${secondBudget}`);

      // Makes sure the second album's budget is large enough
      if (secondBudget < transferAmount) {
        throw new Error(
          `The second album's budget (${secondBudget}) is less than the transfer amount (${transferAmount}).`
        );
      }
    }),

    // Reads the first album's budget
    transaction.read('Albums', queryTwo).then(results => {
      // Gets first album's budget
      const rows = results[0].map(row => row.toJSON());
      firstBudget = rows[0].MarketingBudget;
      console.log(`The first album's marketing budget: ${firstBudget}`);
    }),
  ])
    .then(() => {
      console.log(firstBudget, secondBudget);
      // Transfers the budgets between the albums
      firstBudget += transferAmount;
      secondBudget -= transferAmount;

      console.log(firstBudget, secondBudget);

      // Updates the database
      // Note: Cloud Spanner interprets Node.js numbers as FLOAT64s, so they
      // must be converted (back) to strings before being inserted as INT64s.
      transaction.update('Albums', [
        {
          SingerId: '1',
          AlbumId: '1',
          MarketingBudget: firstBudget.toString(),
        },
        {
          SingerId: '2',
          AlbumId: '2',
          MarketingBudget: secondBudget.toString(),
        },
      ]);
    })
    .then(() => {
      // Commits the transaction and send the changes to the database
      return transaction.commit();
    })
    .then(() => {
      console.log(
        `Successfully executed read-write transaction to transfer ${transferAmount} from Album 2 to Album 1.`
      );
    })
    .catch(err => {
      console.error('ERROR:', err);
    })
    .then(() => {
      // Closes the database when finished
      return database.close();
    });
});

PHP

use Google\Cloud\Spanner\SpannerClient;
use Google\Cloud\Spanner\Transaction;
use UnexpectedValueException;

/**
 * Performs a read-write transaction to update two sample records in the
 * database.
 *
 * This will transfer 200,000 from the `MarketingBudget` field for the second
 * Album to the first Album. If the `MarketingBudget` for the second Album is
 * too low, it will raise an exception.
 *
 * Before running this sample, you will need to run the `update_data` sample
 * to populate the fields.
 * Example:
 * ```
 * read_write_transaction($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function read_write_transaction($instanceId, $databaseId)
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $database->runTransaction(function (Transaction $t) use ($spanner) {
        $transferAmount = 200000;

        // Read the second album's budget.
        $secondAlbumKey = [2,2];
        $secondAlbumKeySet = $spanner->keySet(['keys' => [$secondAlbumKey]]);
        $secondAlbumResult = $t->read(
            'Albums',
            $secondAlbumKeySet,
            ['MarketingBudget'],
            ['limit' => 1]
        );

        $firstRow = $secondAlbumResult->rows()->current();
        $secondAlbumBudget = $firstRow['MarketingBudget'];
        if ($secondAlbumBudget < $transferAmount) {
            // Throwing an exception will automatically roll back the transaction.
            throw new UnexpectedValueException(
                'The second album\'s budget is lower than the transfer amount: ' . $transferAmount
            );
        }

        $firstAlbumKey = [1,1];
        $firstAlbumKeySet = $spanner->keySet(['keys' => [$firstAlbumKey]]);
        $firstAlbumResult = $t->read(
            'Albums',
            $firstAlbumKeySet,
            ['MarketingBudget'],
            ['limit' => 1]
        );

        // Read the first album's budget.
        $firstRow = $firstAlbumResult->rows()->current();
        $firstAlbumBudget = $firstRow['MarketingBudget'];

        // Update the budgets.
        $secondAlbumBudget -= $transferAmount;
        $firstAlbumBudget += $transferAmount;
        printf('Setting first album\'s budget to %s and the second album\'s ' .
            'budget to %s.' . PHP_EOL, $firstAlbumBudget, $secondAlbumBudget);

        // Update the rows.
        $t->updateBatch('Albums', [
            ['SingerId' => 1, 'AlbumId' => 1, 'MarketingBudget' => $firstAlbumBudget],
            ['SingerId' => 2, 'AlbumId' => 2, 'MarketingBudget' => $secondAlbumBudget],
        ]);

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

        print('Transaction complete.' . PHP_EOL);
    });
}

Python

def read_write_transaction(instance_id, database_id):
    """Performs a read-write transaction to update two sample records in the
    database.

    This will transfer 200,000 from the `MarketingBudget` field for the second
    Album to the first Album. If the `MarketingBudget` is too low, it will
    raise an exception.

    Before running this sample, you will need to run the `update_data` sample
    to populate the fields.
    """
    spanner_client = spanner.Client()
    instance = spanner_client.instance(instance_id)
    database = instance.database(database_id)

    def update_albums(transaction):
        # Read the second album budget.
        second_album_keyset = spanner.KeySet(keys=[(2, 2)])
        second_album_result = transaction.read(
            table='Albums', columns=('MarketingBudget',),
            keyset=second_album_keyset, limit=1)
        second_album_row = list(second_album_result)[0]
        second_album_budget = second_album_row[0]

        transfer_amount = 200000

        if second_album_budget < transfer_amount:
            # Raising an exception will automatically roll back the
            # transaction.
            raise ValueError(
                'The second album doesn\'t have enough funds to transfer')

        # Read the first album's budget.
        first_album_keyset = spanner.KeySet(keys=[(1, 1)])
        first_album_result = transaction.read(
            table='Albums', columns=('MarketingBudget',),
            keyset=first_album_keyset, limit=1)
        first_album_row = list(first_album_result)[0]
        first_album_budget = first_album_row[0]

        # Update the budgets.
        second_album_budget -= transfer_amount
        first_album_budget += transfer_amount
        print(
            'Setting first album\'s budget to {} and the second album\'s '
            'budget to {}.'.format(
                first_album_budget, second_album_budget))

        # Update the rows.
        transaction.update(
            table='Albums',
            columns=(
                'SingerId', 'AlbumId', 'MarketingBudget'),
            values=[
                (1, 1, first_album_budget),
                (2, 2, second_album_budget)])

    database.run_in_transaction(update_albums)

    print('Transaction complete.')

Ruby

# project_id  = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"

require "google/cloud/spanner"

spanner         = Google::Cloud::Spanner.new project: project_id
client          = spanner.client instance_id, database_id
transfer_amount = 200_000

client.transaction do |transaction|
  first_album  = transaction.read("Albums", [:MarketingBudget], keys: [[1, 1]]).rows.first
  second_album = transaction.read("Albums", [:MarketingBudget], keys: [[2, 2]]).rows.first

  raise "The second album does not have enough funds to transfer" if second_album[:MarketingBudget] < transfer_amount

  new_first_album_budget  = first_album[:MarketingBudget] + transfer_amount
  new_second_album_budget = second_album[:MarketingBudget] - transfer_amount

  transaction.update "Albums", [
    { SingerId: 1, AlbumId: 1, MarketingBudget: new_first_album_budget  },
    { SingerId: 2, AlbumId: 2, MarketingBudget: new_second_album_budget }
  ]
end

puts "Transaction complete"

Semantik

Serialisierbarkeit und externe Konsistenz

Cloud-Spanner bietet Serialisierbarkeit, das heißt, dass alle Transaktionen so erscheinen, als ob sie in einer seriellen Reihenfolge ausgeführt würden, selbst wenn einige der Lese-, Schreib- und anderen Vorgänge bestimmter Transaktionen tatsächlich parallel stattfanden. Cloud Spanner weist Commit-Zeitstempel zu, die der Reihenfolge übernommener Transaktionen entsprechen, um dieses Attribut zu implementieren. Tatsächlich bietet Cloud Spanner eine höhere Garantie als Serialisierbarkeit, nämlich externe Konsistenz: Transaktionen werden in einer Reihenfolge übernommen, die von deren Commit-Zeitstempeln widergespiegelt wird. Diese Commit-Zeitstempel spiegeln Echtzeit wider, sodass sie mit Ihrer Uhr verglichen werden können. Lesevorgänge in einer Transaktion erkennen alles, was vor dem Commit der Transaktion übergeben wurde, und Schreibvorgänge werden von allem erkannt, was nach dem Commit der Transaktion gestartet wird.

Angenommen, zwei Transaktionen werden so wie im folgenden Diagramm ausgeführt:

Zeitachse, die die Ausführung von zwei Transaktionen anzeigt, die dieselben Daten lesen

Transaktion Txn1 in Blau liest einige Daten A, puffert einen Schreibvorgang in A und wird erfolgreich übergeben. Transaktion Txn2 in Grün startet nach Txn1, liest einige der Daten B und liest dann die Daten A. Da Txn2 den Wert von A liest, nachdem Txn1 den Schreibvorgang an A übergeben hat, erkennt Txn2 die Auswirkung des Schreibvorgangs von Txn1 an A, obwohl Txn2 gestartet wurde, bevor Txn1 abgeschlossen war.

Auch wenn es zeitliche Überlappungen gibt, in denen Txn1 und Txn2 gleichzeitig ausgeführt werden, gilt für ihre Commit-Zeitstempel c1 und c2 eine lineare Transaktionsreihenfolge. Das heißt, dass alle Auswirkungen der Lese- und Schreibvorgänge von Txn1 scheinbar zu einem einzigen Zeitpunkt (c1) und alle Auswirkungen der Lese- und Schreibvorgänge von Txn2 scheinbar zu einem einzigen Zeitpunkt (c2) stattgefunden haben. Darüber hinaus gilt c1 < c2 (dadurch wird garantiert, dass Txn1 und Txn2 Schreibvorgänge übergeben haben; dies gilt auch, wenn die Schreibvorgänge auf verschiedenen Geräten ausgeführt wurden), wobei die Reihenfolge Txn1 vor Txn2 berücksichtigt wird. (Wenn allerdings Txn2 nur Lesevorgänge in der Transaktion ausgeführt hat, gilt c1 <= c2.)

Lesevorgänge erkennen ein Präfix des Commit-Verlaufs. Wenn ein Lesevorgang die Auswirkung von Txn2 erkennt, erkennt er auch die Auswirkung von Txn1. Alle Transaktionen, die erfolgreich Commits ausführen, haben dieses Attribut.

Transaktionslesevorgänge erkennen die Schreibvorgänge der gleichen Transaktion nicht. Sie sollten beachten, dass Leseoperationen die Schreibvorgänge nicht erkennen, die in derselben Transaktion gepuffert werden. Schreibvorgänge werden bis zum Ende der Transaktion gepuffert und sind für keine Lesevorgänge sichtbar, bis die Transaktion übergeben wird. Dies liegt daran, dass sie bei der Pufferung von Mutationen lokal in Ihrem Client gespeichert und erst zum Commit-Zeitpunkt an den Server gesendet werden. Lesevorgänge werden jedoch direkt an den Server gesendet.

Lese- und Schreibgarantien

Wenn ein Aufruf zum Ausführen einer Transaktion fehlschlägt, hängen Ihre Lese- und Schreibgarantien davon ab, welcher Fehler beim zugrunde liegenden Commit-Aufruf für das Fehlschlagen verantwortlich war.

Zum Beispiel bedeutet das Auftreten eines Fehlers der Art "Zeile nicht gefunden" oder "Zeile existiert bereits", dass beim Schreiben der gepufferten Mutationen ein Fehler aufgetreten ist, z. B. dass eine Zeile, die der Client zu aktualisieren versucht, nicht vorhanden ist. In diesem Fall sind die Lesevorgänge garantiert konsistent, die Schreibvorgänge werden nicht angewendet und das Nicht-Vorhandensein einer Zeile ist ebenfalls mit den Lesevorgängen garantiert konsistent.

Transaktionen abbrechen

Asynchrone Leseoperationen können jederzeit vom Nutzer abgebrochen werden (z. B., wenn eine Operation auf höherer Ebene abgebrochen wird oder Sie entscheiden, einen Lesevorgang basierend auf den ersten Ergebnissen zu stoppen, die vom Lesevorgang empfangen wurden), ohne andere existierende Operationen innerhalb der Transaktion zu beeinflussen.

Auch wenn Sie versucht haben, den Lesevorgang abzubrechen, garantiert Cloud Spanner nicht, dass der Lesevorgang tatsächlich abgebrochen wird. Nachdem Sie den Abbruch eines Lesevorgangs angefordert haben, kann dieser Lesevorgang immer noch erfolgreich abgeschlossen werden oder aus einem anderen Grund fehlschlagen (z. B. Abbruch). Darüber hinaus kann der abgebrochene Lesevorgang noch einige Ergebnisse liefern. Diese möglicherweise unvollständigen Ergebnisse werden als Teil des Transaktions-Commits validiert.

Beachten Sie, dass im Gegensatz zu Lesevorgängen das Abbrechen eines Transaktion-Commit-Vorgangs zum Abbruch der Transaktion führt (es sei denn, die Transaktion wurde bereits übergeben oder ist aus einem anderen Grund fehlgeschlagen).

Leistung

Sperren

Mit Cloud Spanner können mehrere Clients gleichzeitig mit derselben Datenbank interagieren. Damit die Konsistenz der vielen simultanen Transaktionen garantiert wird, verwendet Cloud Spanner eine Kombination von gemeinsamen Sperren und exklusiven Sperren, um den Zugriff auf die Daten zu steuern. Wenn Sie einen Lesevorgang als Teil einer Transaktion ausführen, erfasst Cloud Spanner gemeinsam genutzte Lesesperren, wodurch andere Lesevorgänge weiterhin auf die Daten zugreifen können, bis die Transaktion zum Commit bereit ist. Wenn sich Ihre Transaktion im Commit befindet und Schreibvorgänge angewendet werden, versucht die Transaktion, ein Upgrade auf eine exklusive Sperre auszuführen. Sie blockiert neue gemeinsam genutzte Lesesperren für die Daten, wartet darauf, dass vorhandene gemeinsam genutzte Lesesperren bereinigt werden, und setzt eine exklusive Sperre für den exklusiven Zugriff auf die Daten ein.

Hinweise zu Sperren:

  • Sperren werden mit der Granularität von Zeile und Spalte vorgenommen. Wenn mit der Transaktion T1 die Spalte "A" der Zeile "foo" gesperrt wurde und mit Transaktion T2 die Spalte "B" der Zeile "foo" geschrieben werden soll, besteht kein Konflikt.
  • Schreibvorgänge in einem Datenelement, das nicht auch die Daten liest, die gerade geschrieben werden (auch "blind writes" genannt) stehen in keinem Konflikt zu anderen Blind Writers desselben Elements (der Commit-Zeitstempel der einzelnen Schreibvorgänge bestimmt die Reihenfolge, in der er auf die Datenbank angewendet wird). Eine Konsequenz daraus ist, dass Cloud Spanner nur ein Upgrade auf eine exklusive Sperre ausführen muss, wenn Sie die Daten, die Sie schreiben, gelesen haben. Andernfalls verwendet Cloud Spanner eine gemeinsam genutzte Sperre, die als vom Autor gemeinsam genutzte Sperre bezeichnet wird.

Deadlock-Erkennung

Cloud Spanner erkennt, wenn mehrere Transaktionen blockiert werden, und zwingt alle bis auf eine der Transaktionen zum Abbrechen. Betrachten Sie zum Beispiel das folgende Szenario: Transaktion Txn1 enthält eine Sperre für Datensatz A und wartet auf eine Sperre für Datensatz B und Txn2 enthält Sperre für Datensatz B und wartet auf eine Sperre für Datensatz A. Die einzige Möglichkeit, in dieser Situation voranzukommen, besteht darin, eine der Transaktionen abzubrechen, damit die Sperre freigegeben wird und die andere Transaktion fortgesetzt werden kann.

Cloud Spanner verwendet den Standardalgorithmus "wound-wait" für die Deadlock-Erkennung: Cloud Spanner verfolgt das Alter jeder Transaktion, die in Konflikt stehende Sperren anfordert, und ermöglicht es älteren Transaktionen, jüngere Transaktionen abzubrechen (wobei "älter" heißt, dass die Transaktion früher begonnen hat). Dadurch, dass ältere Transaktionen Priorität erhalten, stellt Cloud Spanner sicher, dass jede Transaktion die Möglichkeit bekommt, Sperren zu erhalten, solange sie alt genug wird, um Priorität vor anderen Transaktionen zu bekommen. Zum Beispiel kann eine Transaktion mit einer von einem Leser freigegebenen Sperre von einer älteren Transaktion gesperrt werden, die eine von Autoren freigegebene Sperre benötigt.

Verteilte Transaktionen

Cloud Spanner ist leistungsstark genug, um verteilte Transaktionen auszuführen. Dabei handelt es sich um Transaktionen, die viele Teile der Datenbank betreffen, auch wenn sie sich auf verschiedenen Servern befinden. Diese Leistung führt im Wesentlichen zu Leistungskosten, die mit denen von Transaktionen an nur einem Standort zu vergleichen sind.

Gibt es allgemeine Richtlinien darüber, wann eine Transaktion verteilt werden kann? Intern kann Cloud Spanner die Verantwortung für Zeilen in der Datenbank auf mehrere Server verteilen. Eine Zeile und die entsprechenden Zeilen in verschränkten Tabellen werden in der Regel von demselben Server verarbeitet, so wie bei zwei Zeilen in derselben Tabelle mit ähnlichen Schlüsseln. Cloud Spanner kann Transaktionen mit Zeilen über mehrere Server ausführen. Allerdings gilt als Faustregel, dass Transaktionen, die viele Zeilen an einem Standort betreffen, schneller und günstiger sind als Transaktionen, die viele in der Datenbank oder in einer großen Tabelle verteilte Zeilen betreffen.

Hier einige andere Leistungsoptimierungen, die Sie beachten sollten: Jede Transaktion darf nur die Lese- und Schreibvorgänge enthalten, die atomar angewendet werden sollen, und nicht diejenigen, die mit einem einzelnen Leseaufruf oder einem Schreibvorgang oder in einer separaten Transaktion angewendet werden könnten.

Schreibgeschützte Transaktionen

Zusätzlich zu sperrenden Lese-Schreib-Transaktionen bietet Cloud Spanner schreibgeschützte Transaktionen.

Verwenden Sie eine schreibgeschützte Transaktion, wenn Sie mehr als einen Lesevorgang mit demselben Zeitstempel ausführen müssen. Wenn Sie Ihren Lesevorgang mithilfe einer einzelnen Lesemethode von Cloud Spanner ausdrücken können, sollten Sie stattdessen diese einzelne Lesemethode verwenden. Die Leistung bei der Verwendung eines solchen einzelnen Leseaufrufs sollte mit der Leistung eines einzelnen Lesevorgangs vergleichbar sein, der in einer schreibgeschützten Transaktion ausgeführt wird.

Da schreibgeschützte Transaktionen keine Schreibvorgänge ausführen, haben sie keine Sperren und blockieren andere Transaktionen nicht. Schreibgeschützte Transaktionen erkennen ein konsistentes Präfix des Commit-Verlaufs der Transaktion, damit Ihre Anwendung immer konsistente Daten erhält.

Attribute

Eine schreibgeschützte Transaktion in Cloud Spanner führt eine Reihe von Lesevorgängen zu einem einzigen logischen Zeitpunkt aus, sowohl aus der Sicht der schreibgeschützten Transaktion selbst als auch aus der Perspektive anderer Leser und Autoren in der Cloud Spanner-Datenbank. Dies bedeutet, dass schreibgeschützte Transaktionen immer einen konsistenten Zustand der Datenbank zu einem ausgewählten Punkt im Transaktionsverlauf erkennen.

Schnittstelle

Cloud Spanner bietet eine Schnittstelle zum Ausführen eines Arbeitsablaufs im Rahmen einer schreibgeschützten Transaktion mit Wiederholungen für Transaktionsabbrüche.

Beispiel

Im Folgenden wird gezeigt, wie eine schreibgeschützte Transaktion verwendet werden kann, um konsistente Daten für zwei Lesevorgänge zum selben Zeitstempel zu erhalten:

C#

string connectionString =
$"Data Source=projects/{projectId}/instances/{instanceId}"
+ $"/databases/{databaseId}";
// Gets a transaction object that captures the database state
// at a specific point in time.
using (TransactionScope scope = new TransactionScope(
    TransactionScopeAsyncFlowOption.Enabled))
{
    // Create connection to Cloud Spanner.
    using (var connection = new SpannerConnection(connectionString))
    {
        // Open the connection, making the implicitly created
        // transaction read only when it connects to the outer
        // transaction scope.
        await connection.OpenAsReadOnlyAsync()
            .ConfigureAwait(false);
        var cmd = connection.CreateSelectCommand(
            "SELECT SingerId, AlbumId, AlbumTitle FROM Albums");
        // Read #1.
        using (var reader = await cmd.ExecuteReaderAsync())
        {
            while (await reader.ReadAsync())
            {
                Console.WriteLine("SingerId : "
                    + reader.GetFieldValue<string>("SingerId")
                    + " AlbumId : "
                    + reader.GetFieldValue<string>("AlbumId")
                    + " AlbumTitle : "
                    + reader.GetFieldValue<string>("AlbumTitle"));
            }
        }
        // Read #2. Even if changes occur in-between the reads,
        // the transaction ensures that Read #1 and Read #2
        // return the same data.
        using (var reader = await cmd.ExecuteReaderAsync())
        {
            while (await reader.ReadAsync())
            {
                Console.WriteLine("SingerId : "
                    + reader.GetFieldValue<string>("SingerId")
                    + " AlbumId : "
                    + reader.GetFieldValue<string>("AlbumId")
                    + " AlbumTitle : "
                    + reader.GetFieldValue<string>("AlbumTitle"));
            }
        }
    }
    scope.Complete();
    Console.WriteLine("Transaction complete.");
}

Go

func readOnlyTransaction(ctx context.Context, w io.Writer, client *spanner.Client) error {
	ro := client.ReadOnlyTransaction()
	defer ro.Close()
	stmt := spanner.Statement{SQL: `SELECT SingerId, AlbumId, AlbumTitle FROM Albums`}
	iter := ro.Query(ctx, stmt)
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			break
		}
		if err != nil {
			return err
		}
		var singerID int64
		var albumID int64
		var albumTitle string
		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %d %s\n", singerID, albumID, albumTitle)
	}

	iter = ro.Read(ctx, "Albums", spanner.AllKeys(), []string{"SingerId", "AlbumId", "AlbumTitle"})
	defer iter.Stop()
	for {
		row, err := iter.Next()
		if err == iterator.Done {
			return nil
		}
		if err != nil {
			return err
		}
		var singerID int64
		var albumID int64
		var albumTitle string
		if err := row.Columns(&singerID, &albumID, &albumTitle); err != nil {
			return err
		}
		fmt.Fprintf(w, "%d %d %s\n", singerID, albumID, albumTitle)
	}
}

Java

static void readOnlyTransaction(DatabaseClient dbClient) {
  // ReadOnlyTransaction must be closed by calling close() on it to release resources held by it.
  // We use a try-with-resource block to automatically do so.
  try (ReadOnlyTransaction transaction = dbClient.readOnlyTransaction()) {
    ResultSet queryResultSet =
        transaction.executeQuery(
            Statement.of("SELECT SingerId, AlbumId, AlbumTitle FROM Albums"));
    while (queryResultSet.next()) {
      System.out.printf(
          "%d %d %s\n",
          queryResultSet.getLong(0), queryResultSet.getLong(1), queryResultSet.getString(2));
    }
    // We use a try-with-resource block to automatically release resources held by ResultSet.
    try (ResultSet readResultSet =
        transaction.read(
            "Albums", KeySet.all(), Arrays.asList("SingerId", "AlbumId", "AlbumTitle"))) {
      while (readResultSet.next()) {
        System.out.printf(
            "%d %d %s\n",
            readResultSet.getLong(0), readResultSet.getLong(1), readResultSet.getString(2));
      }
    }
  }
}

Node.js

// Imports the Google Cloud client library
const {Spanner} = require('@google-cloud/spanner');

/**
 * TODO(developer): Uncomment the following lines before running the sample.
 */
// const projectId = 'my-project-id';
// const instanceId = 'my-instance';
// const databaseId = 'my-database';

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

// Gets a reference to a Cloud Spanner instance and database
const instance = spanner.instance(instanceId);
const database = instance.database(databaseId);

// Gets a transaction object that captures the database state
// at a specific point in time
database.getSnapshot(async (err, transaction) => {
  if (err) {
    console.error(err);
    return;
  }
  const queryOne = 'SELECT SingerId, AlbumId, AlbumTitle FROM Albums';

  try {
    // Read #1, using SQL
    const [qOneRows] = await transaction.run(queryOne);

    qOneRows.forEach(row => {
      const json = row.toJSON();
      console.log(
        `SingerId: ${json.SingerId}, AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}`
      );
    });

    const queryTwo = {
      columns: ['SingerId', 'AlbumId', 'AlbumTitle'],
    };

    // Read #2, using the `read` method. Even if changes occur
    // in-between the reads, the transaction ensures that both
    // return the same data.
    const [qTwoRows] = await transaction.read('Albums', queryTwo);

    qTwoRows.forEach(row => {
      const json = row.toJSON();
      console.log(
        `SingerId: ${json.SingerId}, AlbumId: ${json.AlbumId}, AlbumTitle: ${json.AlbumTitle}`
      );
    });

    console.log('Successfully executed read-only transaction.');
  } catch (err) {
    console.error('ERROR:', err);
  } finally {
    transaction.end();
    // Close the database when finished.
    await database.close();
  }
});

PHP

use Google\Cloud\Spanner\SpannerClient;

/**
 * Reads data inside of a read-only transaction.
 *
 * Within the read-only transaction, or "snapshot", the application sees
 * consistent view of the database at a particular timestamp.
 * Example:
 * ```
 * read_only_transaction($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function read_only_transaction($instanceId, $databaseId)
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $snapshot = $database->snapshot();
    $results = $snapshot->execute(
        'SELECT SingerId, AlbumId, AlbumTitle FROM Albums'
    );
    print('Results from the first read:' . PHP_EOL);
    foreach ($results as $row) {
        printf('SingerId: %s, AlbumId: %s, AlbumTitle: %s' . PHP_EOL,
            $row['SingerId'], $row['AlbumId'], $row['AlbumTitle']);
    }

    // Perform another read using the `read` method. Even if the data
    // is updated in-between the reads, the snapshot ensures that both
    // return the same data.
    $keySet = $spanner->keySet(['all' => true]);
    $results = $database->read(
        'Albums',
        $keySet,
        ['SingerId', 'AlbumId', 'AlbumTitle']
    );

    print('Results from the second read:' . PHP_EOL);
    foreach ($results->rows() as $row) {
        printf('SingerId: %s, AlbumId: %s, AlbumTitle: %s' . PHP_EOL,
            $row['SingerId'], $row['AlbumId'], $row['AlbumTitle']);
    }
}

Python

def read_only_transaction(instance_id, database_id):
    """Reads data inside of a read-only transaction.

    Within the read-only transaction, or "snapshot", the application sees
    consistent view of the database at a particular timestamp.
    """
    spanner_client = spanner.Client()
    instance = spanner_client.instance(instance_id)
    database = instance.database(database_id)

    with database.snapshot(multi_use=True) as snapshot:
        # Read using SQL.
        results = snapshot.execute_sql(
            'SELECT SingerId, AlbumId, AlbumTitle FROM Albums')

        print('Results from first read:')
        for row in results:
            print(u'SingerId: {}, AlbumId: {}, AlbumTitle: {}'.format(*row))

        # Perform another read using the `read` method. Even if the data
        # is updated in-between the reads, the snapshot ensures that both
        # return the same data.
        keyset = spanner.KeySet(all_=True)
        results = snapshot.read(
            table='Albums',
            columns=('SingerId', 'AlbumId', 'AlbumTitle',),
            keyset=keyset,)

        print('Results from second read:')
        for row in results:
            print(u'SingerId: {}, AlbumId: {}, AlbumTitle: {}'.format(*row))

Ruby

# project_id  = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"

require "google/cloud/spanner"

spanner = Google::Cloud::Spanner.new project: project_id
client  = spanner.client instance_id, database_id

client.snapshot do |snapshot|
  snapshot.execute("SELECT SingerId, AlbumId, AlbumTitle FROM Albums").rows.each do |row|
    puts "#{row[:AlbumId]} #{row[:AlbumTitle]} #{row[:SingerId]}"
  end

  # Even if changes occur in-between the reads, the transaction ensures that
  # both return the same data.
  snapshot.read("Albums", [:AlbumId, :AlbumTitle, :SingerId]).rows.each do |row|
    puts "#{row[:AlbumId]} #{row[:AlbumTitle]} #{row[:SingerId]}"
  end
end

Partitionierte DML-Transaktionen

Mit partitionierten DML-Anweisungen (Partitioned Data Manipulation Language) können Sie umfangreiche Anweisungen des Typs UPDATE und DELETE ausführen, ohne die Transaktionslimits zu überschreiten oder eine ganze Tabelle zu sperren. Cloud Spanner partitioniert den Schlüsselbereich und führt die DML-Anweisungen in jeder Partition in einer separaten Lese-/Schreibtransaktion aus.

Sie führen DML-Anweisungen in Lese-/Schreibtransaktionen aus, die Sie explizit in Ihrem Code erstellen. Weitere Informationen finden Sie unter DML verwenden.

Attribute

Sie können jeweils nur eine partitionierte DML-Anweisung ausführen, unabhängig davon, ob Sie eine Clientbibliotheksmethode oder das Befehlszeilentool gcloud verwenden.

Partitionierte Transaktionen unterstützen kein Commit oder Rollback. Cloud Spanner führt die DML-Anweisung sofort aus und wendet sie an. Wenn Sie den Vorgang abbrechen oder der Vorgang fehlschlägt, bricht Cloud Spanner alle ausgeführten Partitionen ab und startet keine der verbleibenden Partitionen. Cloud Spanner führt für bereits ausgeführte Partitionen kein Rollback aus.

Schnittstelle

Cloud Spanner bietet eine Schnittstelle zum Ausführen einer einzelnen partitionierten DML-Anweisung.

Beispiele

Im folgenden Codebeispiel wird in der Tabelle Albums die Spalte MarketingBudget aktualisiert.

C#

Sie verwenden die Methode ExecutePartitionedUpdateAsync(), um eine partitionierte DML-Anweisung auszuführen.

public static async Task UpdateUsingPartitionedDmlCoreAsync(
    string projectId,
    string instanceId,
    string databaseId)
{
    string connectionString =
        $"Data Source=projects/{projectId}/instances/{instanceId}"
        + $"/databases/{databaseId}";

    // Create connection to Cloud Spanner.
    using (var connection =
        new SpannerConnection(connectionString))
    {
        await connection.OpenAsync();

        SpannerCommand cmd = connection.CreateDmlCommand(
            "UPDATE Albums SET MarketingBudget = 100000 WHERE SingerId > 1"
        );
        long rowCount = await cmd.ExecutePartitionedUpdateAsync();
        Console.WriteLine($"{rowCount} row(s) updated...");
    }
}

Go

Sie verwenden die Methode PartitionedUpdate(), um eine partitionierte DML-Anweisung auszuführen.

func updateUsingPartitionedDML(ctx context.Context, w io.Writer, client *spanner.Client) error {
	stmt := spanner.Statement{SQL: "UPDATE Albums SET MarketingBudget = 100000 WHERE SingerId > 1"}
	rowCount, err := client.PartitionedUpdate(ctx, stmt)
	if err != nil {
		return err
	}
	fmt.Fprintf(w, "%d record(s) updated.\n", rowCount)
	return nil
}

Java

Sie verwenden die Methode executePartitionedUpdate(), um eine partitionierte DML-Anweisung auszuführen.

static void updateUsingPartitionedDml(DatabaseClient dbClient) {
  String sql = "UPDATE Albums SET MarketingBudget = 100000 WHERE SingerId > 1";
  long rowCount = dbClient.executePartitionedUpdate(Statement.of(sql));
  System.out.printf("%d records updated.\n", rowCount);
}

Node.js

Sie verwenden die Methode runPartitionedUpdate(), um eine partitionierte DML-Anweisung auszuführen.

// Imports the Google Cloud client library
const {Spanner} = require('@google-cloud/spanner');

/**
 * TODO(developer): Uncomment the following lines before running the sample.
 */
// const projectId = 'my-project-id';
// const instanceId = 'my-instance';
// const databaseId = 'my-database';

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

// Gets a reference to a Cloud Spanner instance and database
const instance = spanner.instance(instanceId);
const database = instance.database(databaseId);

try {
  const [rowCount] = await database.runPartitionedUpdate({
    sql: `UPDATE Albums SET MarketingBudget = 100000 WHERE SingerId > 1`,
  });
  console.log(`Successfully updated ${rowCount} records.`);
} catch (err) {
  console.error('ERROR:', err);
} finally {
  // Close the database when finished.
  database.close();
}

PHP

Sie verwenden die Methode executePartitionedUpdate(), um eine partitionierte DML-Anweisung auszuführen.

use Google\Cloud\Spanner\SpannerClient;

/**
 * Updates sample data in the database by partition with a DML statement.
 *
 * This updates the `MarketingBudget` column which must be created before
 * running this sample. You can add the column by running the `add_column`
 * sample or by running this DDL statement against your database:
 *
 *     ALTER TABLE Albums ADD COLUMN MarketingBudget INT64
 *
 * Example:
 * ```
 * update_data($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function update_data_with_partitioned_dml($instanceId, $databaseId)
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $rowCount = $database->executePartitionedUpdate(
        "UPDATE Albums SET MarketingBudget = 100000 WHERE SingerId > 1"
    );

    printf('Updated %d row(s).' . PHP_EOL, $rowCount);
}

Python

Sie verwenden die Methode execute_partitioned_dml(), um eine partitionierte DML-Anweisung auszuführen.

# instance_id = "your-spanner-instance"
# database_id = "your-spanner-db-id"

spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)

row_ct = database.execute_partitioned_dml(
    "UPDATE Albums SET MarketingBudget = 100000 WHERE SingerId > 1"
)

print("{} records updated.".format(row_ct))

Ruby

Sie verwenden die Methode execute_partitioned_update(), um eine partitionierte DML-Anweisung auszuführen.

# project_id  = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"

require "google/cloud/spanner"

spanner = Google::Cloud::Spanner.new project: project_id
client  = spanner.client instance_id, database_id

row_count = client.execute_partition_update(
  "UPDATE Albums SET MarketingBudget = 100000 WHERE SingerId > 1"
)

puts "#{row_count} records updated."

Im folgenden Beispiel werden Zeilen auf Basis der Spalte SingerId aus der Tabelle Singers gelöscht:

C#

public static async Task DeleteUsingPartitionedDmlCoreAsync(
    string projectId,
    string instanceId,
    string databaseId)
{
    string connectionString =
        $"Data Source=projects/{projectId}/instances/{instanceId}"
        + $"/databases/{databaseId}";

    // Create connection to Cloud Spanner.
    using (var connection =
        new SpannerConnection(connectionString))
    {
        await connection.OpenAsync();

        SpannerCommand cmd = connection.CreateDmlCommand(
            "DELETE Singers WHERE SingerId > 10"
        );
        long rowCount = await cmd.ExecutePartitionedUpdateAsync();
        Console.WriteLine($"{rowCount} row(s) deleted...");
    }
}

Go

func deleteUsingPartitionedDML(ctx context.Context, w io.Writer, client *spanner.Client) error {
	stmt := spanner.Statement{SQL: "DELETE Singers WHERE SingerId > 10"}
	rowCount, err := client.PartitionedUpdate(ctx, stmt)
	if err != nil {
		return err

	}
	fmt.Fprintf(w, "%d record(s) deleted.", rowCount)
	return nil
}

Java

static void deleteUsingPartitionedDml(DatabaseClient dbClient) {
  String sql = "DELETE FROM Singers WHERE SingerId > 10";
  long rowCount = dbClient.executePartitionedUpdate(Statement.of(sql));
  System.out.printf("%d records deleted.\n", rowCount);
}

Node.js

// Imports the Google Cloud client library
const {Spanner} = require('@google-cloud/spanner');

/**
 * TODO(developer): Uncomment the following lines before running the sample.
 */
// const projectId = 'my-project-id';
// const instanceId = 'my-instance';
// const databaseId = 'my-database';

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

// Gets a reference to a Cloud Spanner instance and database
const instance = spanner.instance(instanceId);
const database = instance.database(databaseId);

try {
  const [rowCount] = await database.runPartitionedUpdate({
    sql: `DELETE Singers WHERE SingerId > 10`,
  });
  console.log(`Successfully deleted ${rowCount} records.`);
} catch (err) {
  console.error('ERROR:', err);
} finally {
  // Close the database when finished.
  database.close();
}

PHP

use Google\Cloud\Spanner\SpannerClient;

/**
 * Delete sample data in the database by partition with a DML statement.
 *
 * This updates the `MarketingBudget` column which must be created before
 * running this sample. You can add the column by running the `add_column`
 * sample or by running this DDL statement against your database:
 *
 *     ALTER TABLE Albums ADD COLUMN MarketingBudget INT64
 *
 * Example:
 * ```
 * update_data($instanceId, $databaseId);
 * ```
 *
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 */
function delete_data_with_partitioned_dml($instanceId, $databaseId)
{
    $spanner = new SpannerClient();
    $instance = $spanner->instance($instanceId);
    $database = $instance->database($databaseId);

    $rowCount = $database->executePartitionedUpdate(
        "DELETE Singers WHERE SingerId > 10"
    );

    printf('Deleted %d row(s).' . PHP_EOL, $rowCount);
}

Python

# instance_id = "your-spanner-instance"
# database_id = "your-spanner-db-id"
spanner_client = spanner.Client()
instance = spanner_client.instance(instance_id)
database = instance.database(database_id)

row_ct = database.execute_partitioned_dml(
    "DELETE Singers WHERE SingerId > 10"
)

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

Ruby

# project_id  = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"

require "google/cloud/spanner"

spanner = Google::Cloud::Spanner.new project: project_id
client  = spanner.client instance_id, database_id

row_count = client.execute_partition_update(
  "DELETE FROM Singers WHERE SingerId > 10"
)

puts "#{row_count} records deleted."
Hat Ihnen diese Seite weitergeholfen? Teilen Sie uns Ihr Feedback mit:

Feedback geben zu...

Cloud Spanner-Dokumentation