Modificar datos con escritura por lotes

En esta página, se describen las solicitudes de escritura por lotes de Spanner y cómo puedes usarlas para modificar los datos de Spanner.

Puedes usar la escritura por lotes de Spanner para insertar, actualizar o borrar varias filas en tus tablas de Spanner. La escritura por lotes de Spanner admite escrituras de baja latencia sin una operación de lectura y muestra las respuestas a medida que las mutaciones se aplican en lotes. Para usar la escritura por lotes, debes agrupar las mutaciones relacionadas y todas las mutaciones de un grupo se confirman de forma atómica. Las mutaciones entre grupos se aplican en un orden no especificado y son independientes entre sí (no atómicas). Spanner no necesita esperar a que se apliquen todas las mutaciones antes de enviar una respuesta, lo que significa que la escritura por lotes permite una falla parcial. También puedes ejecutar varias escrituras por lotes a la vez. Para obtener más información, consulta Cómo usar la escritura por lotes.

Casos de uso

La escritura por lotes de Spanner es especialmente útil si deseas confirmar una gran cantidad de escrituras sin una operación de lectura, pero no necesitas una transacción atómica para todas las mutaciones.

Si deseas agrupar tus solicitudes DML en lotes, usa el DML por lotes para modificar los datos de Spanner. Para obtener más información sobre las diferencias entre el DML y las mutaciones, consulta Comparación entre el DML y las mutaciones.

Para las solicitudes de mutación única, recomendamos usar una transacción de lectura y escritura de bloqueo.

Limitaciones

La escritura por lotes de Spanner tiene las siguientes limitaciones:

  • La escritura por lotes de Spanner no está disponible con la consola de Google Cloud ni Google Cloud CLI. Solo está disponible con las APIs de REST y RPC y la biblioteca cliente de Java de Spanner.

  • La protección contra la repetición no es compatible con la escritura por lotes. Es posible que las mutaciones se apliquen más de una vez, y una mutación que se aplique más de una vez podría generar un error. Por ejemplo, si se vuelve a reproducir una mutación de inserción, podría generar un error que ya existe o, si usas claves basadas en la marca de tiempo generadas o de confirmación en la mutación, es posible que se agreguen filas adicionales a la tabla. Te recomendamos que estructures tus escrituras de modo que sean idempotentes para evitar este problema.

  • No puedes revertir una solicitud de escritura por lotes completada. Puedes cancelar una solicitud de escritura por lotes en curso. Si cancelas una escritura por lotes en curso, las mutaciones de los grupos sin completar se revierten. Las mutaciones en los grupos completados se confirman en la base de datos.

  • El tamaño máximo de una solicitud de escritura por lotes es el mismo que el límite de una solicitud de confirmación. Para obtener más información, consulta Límites para crear, leer, actualizar y borrar datos.

Cómo usar la escritura por lotes

Para usar la escritura por lotes, debes tener el permiso spanner.databases.write en la base de datos que deseas modificar. Puedes escribir mutaciones de escritura por lotes de forma no atómica en una sola llamada mediante una llamada de solicitud a REST o a la API de RPC.

Debes agrupar los siguientes tipos de mutación cuando uses la escritura por lotes:

  • Insertar filas con el mismo prefijo de clave primaria en las tablas superiores y secundarias
  • Insertar filas en las tablas con una relación de clave externa entre las tablas
  • Otros tipos de mutaciones relacionadas según el esquema de tu base de datos y la lógica de la aplicación.

También puedes escribir por lotes con la biblioteca cliente de Java de Spanner. En el siguiente ejemplo de código, se actualiza la tabla Singers con filas nuevas.

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

¿Qué sigue?