Cette page a été traduite par l'API Cloud Translation.
Switch to English

Langage de manipulation de données partitionné

Le langage de manipulation de données partitionné (LMD partitionné) est conçu pour les mises à jour et les suppressions en masse :

  • Nettoyage périodique et récupération de mémoire. Il s'agit par exemple de supprimer d'anciennes lignes ou de définir des colonnes sur NULL.
  • Remplissage de nouvelles colonnes avec des valeurs par défaut. Cette opération consiste, par exemple, à utiliser une instruction UPDATE pour définir la valeur d'une nouvelle colonne sur False lorsqu'elle est actuellement à NULL.

LMD et LMD partitionné

Cloud Spanner accepte deux modes d'exécution pour les instructions LMD.

  • Le mode LMD est adapté pour le traitement des transactions. Pour plus d'informations, consultez la section Utiliser LMD.

  • Le mode LMD partitionné permet des opérations à grande échelle et à l'échelle de la base de données avec une incidence réduite sur le traitement des transactions simultanées en partitionnant l'espace clé et en exécutant l'instruction sur des partitions dans des transactions séparées de plus petite portée. Pour plus d'informations, consultez la page Utiliser le LMD partitionné.

Le tableau suivant met en évidence certaines des différences entre les deux modes d'exécution.

LMD LMD partitionné
Les lignes qui ne correspondent pas à la clause WHERE peuvent être verrouillées. Seules les lignes correspondant à la clause WHERE sont verrouillées.
Les limites de taille de transaction s'appliquent. Cloud Spanner gère les limites de transaction et les limites de simultanéité par transaction.
Les instructions n'ont pas besoin d'être idempotentes. Une instruction LMD doit être idempotente pour garantir des résultats cohérents.
Une transaction peut inclure plusieurs instructions LMD et SQL. Une transaction partitionnée ne peut inclure qu'une seule instruction LMD.
Il n'y a aucune restriction sur la complexité des instructions. Les instructions doivent être entièrement partitionnables.
Vous créez des transactions en lecture-écriture dans votre code client. Cloud Spanner crée les transactions.

Partitionnable et idempotent

Lorsqu'une instruction en mode LMD partitionné est exécutée, les lignes d'une partition n'ont pas accès aux lignes des autres partitions. En outre, vous ne pouvez pas choisir la manière dont Cloud Spanner crée les partitions. Le partitionnement garantit l'évolutivité, mais signifie également que les instructions en mode LMD partitionné doivent être entièrement partitionnables. En d'autres termes, l'instruction en mode LMD partitionné doit pouvoir être exprimée comme l'union d'un ensemble d'instructions, chaque instruction accédant à une seule ligne de la table et à aucune autre table. Par exemple, une instruction LMD qui accède à plusieurs tables ou effectue une jointure automatique n'est pas partitionnable. Si l'instruction LMD ne peut pas être partitionnée, Cloud Spanner renvoie l'erreur BadUsage.

Ces instructions LMD sont entièrement partitionnables, car chaque instruction peut être appliquée à une seule ligne de la table :

UPDATE Singers SET Available = TRUE WHERE Available IS NULL

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

Cette instruction LMD n'est pas entièrement partitionnable, car elle accède à plusieurs tables :

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

Cloud Spanner peut exécuter une instruction en mode LMD partitionné plusieurs fois sur certaines partitions en raison de nouvelles tentatives au niveau du réseau. En conséquence, une instruction peut être exécutée plusieurs fois sur une ligne. L'instruction doit donc être idempotente pour renvoyer des résultats cohérents. Une instruction est idempotente si elle renvoie le même résultat lorsqu'elle est exécutée plusieurs fois sur une même ligne.

Cette instruction LMD est idempotente :

UPDATE Singers SET MarketingBudget = 1000 WHERE true

Cette instruction LMD n'est pas idempotente :

UPDATE Singers SET MarketingBudget = 1.5 * MarketingBudget WHERE true

Verrouillage

Cloud Spanner déclenche un verrouillage uniquement lorsqu'une ligne est susceptible d'être mise à jour ou supprimée. Ce comportement est différent de l'exécution en mode LMD, qui peut contenir des lignes verrouillées en lecture ne correspondant pas à la clause WHERE.

Exécution et transactions

Le fait qu'une instruction LMD soit partitionnée ou non dépend de la méthode de la bibliothèque cliente que vous choisissez pour l'exécution. Chaque bibliothèque cliente fournit des méthodes distinctes pour l'exécution en mode LMD et l'exécution en mode LMD partitionné.

Vous ne pouvez exécuter qu'une seule instruction en mode LMD partitionné lors d'un appel à la méthode de la bibliothèque cliente.

Cloud Spanner n'applique pas les instructions en mode LMD partitionné de manière atomique sur l'ensemble de la table. Cependant, Cloud Spanner applique les instructions en mode LMD partitionné de manière atomique sur chaque partition.

Le LMD partitionné ne prend pas en charge le commit ou la restauration. Cloud Spanner s'exécute et applique immédiatement l'instruction LMD.

  • Si vous annulez l'opération, Cloud Spanner annule les partitions en cours d'exécution et ne démarre pas les partitions restantes. Cloud Spanner ne restaure aucune des partitions déjà exécutées.
  • Si l'exécution de l'instruction génère une erreur, l'exécution s'arrête sur toutes les partitions et Cloud Spanner renvoie cette erreur pour l'ensemble de l'opération. Parmi les erreurs, on trouve par exemple les violations des contraintes de type de données, les violations de UNIQUE INDEX et les violations de ON DELETE NO ACTION. En fonction du moment où l'exécution a échoué, l'instruction peut avoir été exécutée avec succès sur certaines partitions et ne jamais avoir été exécutée sur d'autres.

Si l'instruction en mode LMD partitionné réussit, Cloud Spanner l'a exécutée au moins une fois sur chaque partition de la plage de clés.

Nombre de lignes modifiées

Une instruction en mode LMD partitionné renvoie une limite inférieure sur le nombre de lignes modifiées. Il se peut que le nombre de lignes modifiées ne soit pas exact, car rien ne garantit que Cloud Spanner compte toutes les lignes modifiées.

Limites de transaction

Cloud Spanner crée les partitions et les transactions nécessaires à l'exécution d'une instruction en mode LMD partitionné. Les limites de transaction ou les limites de simultanéité par transaction s'appliquent, mais Cloud Spanner tente de maintenir les transactions dans les limites.

Cloud Spanner autorise un maximum de 20 000 instructions en mode LMD partitionné simultanées par base de données.

Fonctionnalités non acceptées

Cloud Spanner n'accepte pas certaines fonctionnalités du LMD partitionné :

  • INSERT n'est pas compatible.
  • Cloud Console : il n'est pas possible d'exécuter des instructions en mode LMD partitionné dans Cloud Console.
  • Plans de requête et profilage : l'outil de ligne de commande gcloud et les bibliothèques clientes n'acceptent pas les plans de requête ni le profilage.

Exemples

L'exemple de code suivant met à jour la colonne MarketingBudget de la table Albums.

C++

Vous utilisez la fonction ExecutePartitionedDml() pour exécuter une instruction DML partitionnée.

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#

Utilisez la méthode ExecutePartitionedUpdateAsync() pour exécuter une instruction en mode LMD partitionné.

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

Utilisez la méthode PartitionedUpdate() pour exécuter une instruction en mode LMD partitionné.


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

Utilisez la méthode executePartitionedUpdate() pour exécuter une instruction en mode LMD partitionné.

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

Utilisez la méthode runPartitionedUpdate() pour exécuter une instruction en mode LMD partitionné.

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

Utilisez la méthode executePartitionedUpdate() pour exécuter une instruction en mode LMD partitionné.

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

Utilisez la méthode execute_partitioned_dml() pour exécuter une instruction en mode LMD partitionné.

# 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

Utilisez la méthode execute_partitioned_update() pour exécuter une instruction en mode LMD partitionné.

# 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."

L'exemple de code suivant supprime les lignes de la table Singers, en fonction de la colonne SingerId.

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."

Étape suivante