Partitionierte Datenbearbeitungssprache

Die partitionierte Datenbearbeitungssprache (Partitioned Data Manipulation Language, DML) wurde für folgende Aktualisierungs- und Löschvorgänge von Bulk-Daten entwickelt:

  • Regelmäßige und automatische Speicherbereinigungsvorgänge. Beispiele: Alte Zeilen löschen oder Spalten auf NULL setzen.
  • Backfilling neuer Spalten mit Standardwerten. Beispiel: Mit einer UPDATE-Anweisung können Sie für den Wert einer neuen Spalte False festlegen, für die aktuell NULL gilt.

DML und partitionierte DML

Cloud Spanner unterstützt zwei Ausführungsmodi für DML-Anweisungen.

  • DML ist für die Transaktionsverarbeitung geeignet.
  • Partitionierte DML ermöglicht umfangreiche, datenbankweite Vorgänge mit minimaler Auswirkung auf die gleichzeitige Transaktionsverarbeitung. Bei diesen Vorgängen wird der Schlüsselbereich partitioniert und die Anweisung wird in separaten, weniger umfangreichen Transaktionen auf die Partitionen angewendet.

Die folgende Tabelle zeigt einen Vergleich der beiden Ausführungsmodi.

DML Partitionierte DML
Zeilen, die nicht mit der WHERE-Klausel übereinstimmen, können gesperrt werden. Nur Zeilen, die mit der WHERE-Klausel übereinstimmen, werden gesperrt.
Die Transaktionsgröße ist begrenzt. Cloud Spanner unterstützt alle Transaktionsgrößen und gleichzeitig ausgeführten Transaktionen.
Anweisungen müssen nicht idempotent sein. DML-Anweisungen müssen idempotent sein, um konsistente Ergebnisse zu liefern.
Transaktionen können mehrere DML- und SQL-Anweisungen enthalten. Partitionierte Transaktionen können nur eine DML-Anweisung enthalten.
Es gibt keine Einschränkungen hinsichtlich der Komplexität von Anweisungen. Anweisungen müssen vollständig partitionierbar sein.
Sie erstellen Lese- und Schreibtransaktionen im Clientcode. Cloud Spanner erstellt die Transaktionen.

Partitionierbar und idempotent

Bei der Ausführung partitionierter DML-Anweisungen haben die Zeilen in einer Partition keinen Zugriff auf die Zeilen in anderen Partitionen. Sie selbst haben keinen Einfluss darauf, wie Cloud Spanner die Partitionen erstellt. Partitionierung gewährleistet Skalierbarkeit, setzt jedoch voraus, dass partitionierte DML-Anweisungen vollständig partitionierbar sind. Das heißt, es muss möglich sein, die partitionierte DML-Anweisung als Vereinigung mehrerer Anweisungen auszudrücken, wobei jede Anweisung auf genau eine Zeile der Tabelle und keine anderen Tabellen zugreift. So sind beispielsweise DML-Anweisungen, die auf mehrere Tabellen zugreifen oder sich selbst verknüpfen (self-join), nicht partitionierbar. Wenn die DML-Anweisung nicht partitionierbar ist, gibt Cloud Spanner den Fehler BadUsage aus.

Diese DML-Anweisungen sind vollständig partitionierbar, da jede Anweisung auf genau eine Zeile in der Tabelle angewendet werden kann:

UPDATE Singers SET Available = TRUE WHERE Available IS NULL

DELETE FROM Concerts
WHERE DATE_DIFF(CURRENT_DATE(), ConcertDate, DAY) > 365

Diese DML-Anweisung kann nicht vollständig partitioniert werden, da sie auf mehrere Tabellen zugreift:

# Not fully partitionable
DELETE FROM Singers WHERE
SingerId NOT IN (SELECT SingerId FROM Concerts);

Es kann vorkommen, dass Cloud Spanner partitionierte DML-Anweisungen aufgrund von Wiederholungen auf Netzwerkebene für einige Partitionen mehrmals ausführt. Dadurch wird eine Anweisung für eine Zeile mehrmals ausgeführt. Die Anweisung muss daher idempotent sein, um konsistente Ergebnisse zu erzielen. Anweisungen sind idempotent, wenn die mehrfache Ausführung für eine Zeile immer zum selben Ergebnis führt.

Diese DML-Anweisung ist idempotent:

UPDATE Singers SET MarketingBudget = 1000 WHERE true

Diese DML-Anweisung ist nicht idempotent:

UPDATE Singers SET MarketingBudget = 1.5 * MarketingBudget WHERE true

Sperren

Cloud Spanner übernimmt Sperren nur für Zeilen, die Kandidaten für Aktualisierungs- oder Löschvorgänge sind. Dieses Verhalten unterscheidet sich von der DML-Ausführung. Hier können auch Zeilen, die nicht mit der WHERE-Klausel übereinstimmen, eine Lesesperre erhalten.

Ausführung und Transaktionen

Ob eine DML-Anweisung partitioniert ist, hängt von der Clientbibliotheksmethode ab, die Sie für die Ausführung verwenden. Jede Clientbibliothek stellt für die DML-Ausführung und die partitionierte DML-Ausführung separate Methoden bereit.

Pro Aufruf der Clientbibliotheksmethode können Sie nur eine partitionierte DML-Anweisung ausführen.

Cloud Spanner wendet partitionierte DML-Anweisungen nicht atomar auf die gesamte Tabelle an. Auf die einzelnen Partitionen werden sie jedoch atomar angewendet.

Partitionierte DML-Anweisungen unterstützen kein Commit oder Rollback. Cloud Spanner führt die DML-Anweisung sofort aus und wendet sie an.

  • Wenn Sie den Vorgang abbrechen, bricht Cloud Spanner die ausgeführten Partitionen ab und startet die verbleibenden Partitionen nicht. Cloud Spanner führt kein Rollback von bereits ausgeführten Partitionen durch.
  • Wenn die Ausführung der Anweisung einen Fehler verursacht, wird die Ausführung in allen Partitionen beendet und Cloud Spanner gibt den Fehler für den gesamten Vorgang zurück. Fehler werden beispielsweise bei Verstößen gegen Datentypeinschränkungen, gegen UNIQUE INDEX und gegen ON DELETE NO ACTION ausgelöst. Je nachdem, an welchem Punkt der Ausführung der Fehler auftritt, ist die Anweisung möglicherweise für einige Partitionen erfolgreich und für andere aber nicht.

Bei einer erfolgreichen Ausführung wurde die partitionierte DML-Anweisung von Cloud Spanner für jede Partition des Schlüsselbereichs mindestens einmal ausgeführt.

Anzahl geänderter Zeilen

Eine partitionierte DML-Anweisung gibt die Zahl der Zeilen aus, die mindestens geändert wurden. Diese Zahl muss nicht exakt sein. Es gibt keine Garantie dafür, dass Cloud Spanner alle geänderten Zeilen gezählt hat.

Transaktionslimits

Cloud Spanner erstellt die Partitionen und Transaktionen, die zum Ausführen einer partitionierten DML-Anweisung erforderlich sind. Es gelten Transaktionslimits und Gleichzeitigkeitslimits pro Transaktion. Cloud Spanner versucht, die Transaktionen innerhalb dieser Limits zu halten.

Cloud Spanner unterstützt pro Datenbank maximal 20.000 partitionierte DML-Anweisungen gleichzeitig.

Nicht unterstützte Funktionen

Die folgenden Features für partitionierte DML-Anweisungen werden von Cloud Spanner nicht unterstützt:

  • INSERT wird nicht unterstützt.
  • Cloud Console: Partitionierte DML-Anweisungen können in der Cloud Console nicht ausgeführt werden.
  • Abfragepläne und Profilerstellung: Das gcloud-Befehlszeilentool und die Clientbibliotheken unterstützen keine Abfragepläne und keine Profilerstellung.

Beispiele

Mit den folgenden Codebeispielen wird die Spalte MarketingBudget der Tabelle Albums aktualisiert.

C++

Zum Ausführen einer partitionierten DML-Anweisung verwenden Sie die Funktion ExecutePartitionedDml().

void DmlPartitionedUpdate(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;
  auto result = client.ExecutePartitionedDml(
      spanner::SqlStatement("UPDATE Albums SET MarketingBudget = 100000"
                            " WHERE SingerId > 1"));
  if (!result) throw std::runtime_error(result.status().message());
  std::cout << "Update was successful [spanner_dml_partitioned_update]\n";
}

C#

Zum Ausführen einer partitionierten DML-Anweisung verwenden Sie die Methode ExecutePartitionedUpdateAsync().

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

Zum Ausführen einer partitionierten DML-Anweisung verwenden Sie die Methode PartitionedUpdate().


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
)

func updateUsingPartitionedDML(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	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

Zum Ausführen einer partitionierten DML-Anweisung verwenden Sie die Methode executePartitionedUpdate().

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

Zum Ausführen einer partitionierten DML-Anweisung verwenden Sie die Methode runPartitionedUpdate().

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

Zum Ausführen einer partitionierten DML-Anweisung verwenden Sie die Methode executePartitionedUpdate().

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

Zum Ausführen einer partitionierten DML-Anweisung verwenden Sie die Methode execute_partitioned_dml().

# 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

Zum Ausführen einer partitionierten DML-Anweisung verwenden Sie die Methode execute_partitioned_update().

# 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 Codebeispiel werden Zeilen aus der Tabelle Singers anhand der Spalte SingerId gelöscht.

C++

void DmlPartitionedDelete(google::cloud::spanner::Client client) {
  namespace spanner = ::google::cloud::spanner;
  auto result = client.ExecutePartitionedDml(
      spanner::SqlStatement("DELETE FROM Singers WHERE SingerId > 10"));
  if (!result) throw std::runtime_error(result.status().message());
  std::cout << "Delete was successful [spanner_dml_partitioned_delete]\n";
}

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 FROM Singers WHERE SingerId > 10"
        );
        long rowCount = await cmd.ExecutePartitionedUpdateAsync();
        Console.WriteLine($"{rowCount} row(s) deleted...");
    }
}

Go


import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/spanner"
)

func deleteUsingPartitionedDML(w io.Writer, db string) error {
	ctx := context.Background()
	client, err := spanner.NewClient(ctx, db)
	if err != nil {
		return err
	}
	defer client.Close()

	stmt := spanner.Statement{SQL: "DELETE FROM 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 FROM 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 FROM 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 FROM 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."