Modifier des données à l'aide d'une écriture par lot

Cette page décrit les requêtes d'écriture par lot Spanner et explique comment les utiliser pour modifier vos données Spanner.

Vous pouvez utiliser l'écriture par lot Spanner pour insérer, mettre à jour ou supprimer plusieurs lignes dans vos tables Spanner. L'écriture par lot Spanner permet les écritures à faible latence sans opération de lecture, et renvoie des réponses lorsque des mutations sont appliquées par lots. Pour utiliser l'écriture par lot, vous regroupez les mutations associées, et toutes les mutations d'un groupe sont validées de manière atomique. Les mutations entre groupes sont appliquées dans un ordre non spécifié et sont indépendantes les unes des autres (non atomiques). Spanner n'a pas besoin d'attendre que toutes les mutations soient appliquées avant d'envoyer une réponse. L'écriture par lot autorise donc un échec partiel. Vous pouvez également exécuter plusieurs écritures par lot à la fois. Pour en savoir plus, consultez la section Utiliser l'écriture par lot.

Cas d'utilisation

L'écriture par lot dans Spanner est particulièrement utile si vous souhaitez effectuer un commit d'un grand nombre d'écritures sans opération de lecture, sans nécessiter de transaction atomique pour toutes vos mutations.

Si vous souhaitez regrouper vos requêtes LMD, utilisez le traitement par lot en LMD pour modifier vos données Spanner. Pour en savoir plus sur les différences entre le LMD et les mutations, consultez la section Comparer le LMD et les mutations.

Pour les requêtes de mutation unique, nous vous recommandons d'utiliser le verrouillage des transactions en lecture-écriture.

Limites

L'écriture par lot dans Spanner présente les limites suivantes:

  • L'écriture par lot Spanner n'est pas disponible via la console Google Cloud ni Google Cloud CLI. Elle n'est disponible qu'à l'aide des API REST et RPC et de la bibliothèque cliente Java Spanner.

  • La protection contre la relecture n'est pas compatible avec l'écriture par lot. Il est possible que des mutations soient appliquées plusieurs fois, tandis qu'une mutation appliquée plusieurs fois peut entraîner un échec. Par exemple, si une mutation d'insertion est relancée, elle peut produire une erreur déjà existante. Si vous utilisez des clés générées ou basées sur l'horodatage dans la mutation, des lignes supplémentaires peuvent être ajoutées à la table. Pour éviter ce problème, nous vous recommandons de structurer vos écritures de sorte qu'elles soient idempotentes.

  • Vous ne pouvez pas effectuer de rollback pour une requête d'écriture par lot terminée. Vous pouvez annuler une requête d'écriture par lot en cours. Si vous annulez une écriture par lot en cours, les mutations des groupes non terminés sont annulées. Les mutations de groupes terminés sont validées dans la base de données.

  • La taille maximale d'une requête d'écriture par lot est identique à la limite d'une requête de commit. Pour en savoir plus, consultez la section Limites de création, de lecture, de mise à jour et de suppression des données.

Utiliser l'écriture par lot

Pour utiliser l'écriture par lot, vous devez disposer de l'autorisation spanner.databases.write sur la base de données que vous souhaitez modifier. Vous pouvez écrire des mutations de manière non atomique dans un seul appel par lot à l'aide d'un appel de requête REST ou API RPC.

Lorsque vous utilisez une écriture par lot, vous devez regrouper les types de mutations suivants:

  • Insérer des lignes ayant le même préfixe de clé primaire dans les tables parentes et enfants
  • Insérer des lignes dans des tables avec une relation de clé étrangère entre les tables
  • Autres types de mutations associées en fonction du schéma de votre base de données et de la logique d'application.

Vous pouvez également effectuer une écriture par lot à l'aide de la bibliothèque cliente Java de Spanner. L'exemple de code suivant met à jour la table Singers avec de nouvelles lignes.

Java


import com.google.api.gax.rpc.ServerStream;
import com.google.cloud.spanner.DatabaseClient;
import com.google.cloud.spanner.DatabaseId;
import com.google.cloud.spanner.Mutation;
import com.google.cloud.spanner.MutationGroup;
import com.google.cloud.spanner.Options;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerOptions;
import com.google.common.collect.ImmutableList;
import com.google.rpc.Code;
import com.google.spanner.v1.BatchWriteResponse;

public class BatchWriteAtLeastOnceSample {

  /***
   * Assume DDL for the underlying database:
   * <pre>{@code
   *   CREATE TABLE Singers (
   *     SingerId   INT64 NOT NULL,
   *     FirstName  STRING(1024),
   *     LastName   STRING(1024),
   *   ) PRIMARY KEY (SingerId)
   *
   *   CREATE TABLE Albums (
   *     SingerId     INT64 NOT NULL,
   *     AlbumId      INT64 NOT NULL,
   *     AlbumTitle   STRING(1024),
   *   ) PRIMARY KEY (SingerId, AlbumId),
   *   INTERLEAVE IN PARENT Singers ON DELETE CASCADE
   * }</pre>
   */

  private static final MutationGroup MUTATION_GROUP1 =
      MutationGroup.of(
          Mutation.newInsertOrUpdateBuilder("Singers")
              .set("SingerId")
              .to(16)
              .set("FirstName")
              .to("Scarlet")
              .set("LastName")
              .to("Terry")
              .build());
  private static final MutationGroup MUTATION_GROUP2 =
      MutationGroup.of(
          Mutation.newInsertOrUpdateBuilder("Singers")
              .set("SingerId")
              .to(17)
              .set("FirstName")
              .to("Marc")
              .build(),
          Mutation.newInsertOrUpdateBuilder("Singers")
              .set("SingerId")
              .to(18)
              .set("FirstName")
              .to("Catalina")
              .set("LastName")
              .to("Smith")
              .build(),
          Mutation.newInsertOrUpdateBuilder("Albums")
              .set("SingerId")
              .to(17)
              .set("AlbumId")
              .to(1)
              .set("AlbumTitle")
              .to("Total Junk")
              .build(),
          Mutation.newInsertOrUpdateBuilder("Albums")
              .set("SingerId")
              .to(18)
              .set("AlbumId")
              .to(2)
              .set("AlbumTitle")
              .to("Go, Go, Go")
              .build());

  static void batchWriteAtLeastOnce() {
    // TODO(developer): Replace these variables before running the sample.
    final String projectId = "my-project";
    final String instanceId = "my-instance";
    final String databaseId = "my-database";
    batchWriteAtLeastOnce(projectId, instanceId, databaseId);
  }

  static void batchWriteAtLeastOnce(String projectId, String instanceId, String databaseId) {
    try (Spanner spanner =
        SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) {
      DatabaseId dbId = DatabaseId.of(projectId, instanceId, databaseId);
      final DatabaseClient dbClient = spanner.getDatabaseClient(dbId);

      // Creates and issues a BatchWrite RPC request that will apply the mutation groups
      // non-atomically and respond back with a stream of BatchWriteResponse.
      ServerStream<BatchWriteResponse> responses =
          dbClient.batchWriteAtLeastOnce(
              ImmutableList.of(MUTATION_GROUP1, MUTATION_GROUP2),
              Options.tag("batch-write-tag"));

      // Iterates through the results in the stream response and prints the MutationGroup indexes,
      // commit timestamp and status.
      for (BatchWriteResponse response : responses) {
        if (response.getStatus().getCode() == Code.OK_VALUE) {
          System.out.printf(
              "Mutation group indexes %s have been applied with commit timestamp %s",
              response.getIndexesList(), response.getCommitTimestamp());
        } else {
          System.out.printf(
              "Mutation group indexes %s could not be applied with error code %s and "
                  + "error message %s", response.getIndexesList(),
              Code.forNumber(response.getStatus().getCode()), response.getStatus().getMessage());
        }
      }
    }
  }
}

Étapes suivantes