Lenguaje de manipulación de datos particionado

El lenguaje de manipulación de datos particionado (DML particionado) está diseñado para actualizaciones y eliminaciones masivas:

  • Recolección de elementos no usados y limpieza periódicos. Por ejemplo, la eliminación de filas antiguas o la configuración de columnas como NULL.
  • El restablecimiento de columnas nuevas con valores predeterminados. Un ejemplo es el uso de una declaración UPDATE para configurar el valor de una columna nueva como False si, en el momento, se encuentra como NULL.

DML y DML particionado

Cloud Spanner admite dos modos de ejecución para las declaraciones DML.

  • El DML es adecuado para el procesamiento de transacciones.
  • El DML particionado permite operaciones a gran escala en toda la base de datos con un impacto mínimo en el procesamiento simultáneo de transacciones, ya que particiona el espacio clave y ejecuta la declaración en particiones en distintas transacciones de menor alcance.

En la siguiente tabla, se destacan algunas de las diferencias entre los dos modos de ejecución.

DML DML particionado
Las filas que no coincidan con la cláusula WHERE podrían estar bloqueadas. Solo se bloquean las filas que coinciden con la cláusula WHERE.
Se aplican los límites de tamaño de transacciones. Cloud Spanner controla los límites de transacciones y los límites de simultaneidad por transacción.
No es necesario que las declaraciones sean idempotentes. Una declaración DML debe ser idempotente para garantizar resultados coherentes.
Una transacción puede incluir varias instrucciones de SQL y declaraciones DML. Una transacción particionada solo puede incluir una declaración DML.
No hay restricciones para la complejidad de las declaraciones. Las declaraciones deben poder particionarse por completo.
Las transacciones de lectura y escritura se crean en el código del cliente. Cloud Spanner crea las transacciones.

Particionable e idempotente

Cuando se ejecuta una declaración DML particionada, las filas de una partición no tienen acceso a las filas de otras particiones; además, no puedes elegir cómo Cloud Spanner crea las particiones. La partición garantiza la escalabilidad, pero también implica que las declaraciones DML particionadas deben poder particionarse por completo. Es decir, la declaración DML particionada debe poder expresarse como la unión de un conjunto de declaraciones, en la que cada declaración accede a una sola fila de la tabla y no tiene acceso a ninguna otra tabla. Por ejemplo, una declaración DML que accede a varias tablas o realiza una unión de tabla con sí misma no es particionable. Si la declaración DML no es particionable, Cloud Spanner muestra el error BadUsage.

Estas declaraciones DML se pueden particionar por completo, ya que cada una se puede aplicar a una sola fila de la tabla:

UPDATE Singers SET Available = TRUE WHERE Available IS NULL

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

Esta declaración DML no se puede particionar por completo, ya que accede a varias tablas:

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

Cloud Spanner podría ejecutar una declaración DML particionada varias veces en algunas particiones debido a reintentos a nivel de la red. Como resultado, una declaración se podría ejecutar más de una vez en una fila. Por lo tanto, la declaración debe ser idempotente para obtener resultados coherentes. Una declaración es idempotente si, cuando se ejecuta varias veces en una sola fila, genera el mismo resultado.

Esta declaración DML es idempotente:

UPDATE Singers SET MarketingBudget = 1000 WHERE true

Esta declaración DML no es idempotente:

UPDATE Singers SET MarketingBudget = 1.5 * MarketingBudget WHERE true

Bloqueo

Cloud Spanner adquiere un bloqueo solo si una fila es candidata para una actualización o eliminación. Este comportamiento es diferente de la ejecución de DML, que podría bloquear como solo lectura las filas que no coinciden con la cláusula WHERE.

Ejecución y transacciones

En función del método de la biblioteca cliente que elijas para la ejecución, una declaración DML estará particionada o no. Cada biblioteca cliente proporciona distintos métodos para la ejecución de DML y la ejecución de DML particionado.

Solo puedes ejecutar una declaración DML particionada en una llamada al método de la biblioteca cliente.

Cloud Spanner no aplica las declaraciones DML particionadas de forma atómica en toda la tabla. Sin embargo, aplica las declaraciones DML particionadas de forma atómica en cada partición.

El DML particionado no admite la confirmación ni la reversión. Cloud Spanner ejecuta y aplica la declaración DML de forma inmediata.

  • Si cancelas la operación, Cloud Spanner cancela las particiones en ejecución y no inicia las restantes. Cloud Spanner no revierte las particiones que ya se hayan ejecutado.
  • Si la ejecución de la declaración provoca un error, la ejecución se detiene en todas las particiones y Cloud Spanner muestra ese error en toda la operación. Algunos ejemplos de errores son los incumplimientos de las restricciones de tipo de datos, de UNIQUE INDEX y de ON DELETE NO ACTION. En función del momento en el que falló la ejecución, es posible que la declaración se haya ejecutado con éxito en algunas particiones y que nunca se haya ejecutado en otras particiones.

Si la declaración DML particionada se realizó con éxito, Cloud Spanner ejecutó la declaración al menos una vez en cada partición del rango clave.

Recuento de filas modificadas

Una declaración DML particionada muestra un límite inferior en la cantidad de filas modificadas. Es posible que no sea un recuento exacto de la cantidad de filas modificadas, ya que no hay garantía de que Cloud Spanner cuente todas las filas modificadas.

Límites de transacciones

Cloud Spanner crea las particiones y transacciones que necesita para ejecutar una declaración DML particionada. Se aplican los límites de transacciones o los límites de simultaneidad por transacción, pero Cloud Spanner intenta mantener las transacciones dentro de los límites.

Cloud Spanner permite un máximo de 20,000 declaraciones DML particionadas simultáneas por base de datos.

Funciones que no son compatibles

Cloud Spanner no es compatible con algunas funciones de los DML particionados:

  • INSERT no es compatible.
  • Cloud Console: No puedes ejecutar declaraciones DML particionadas en Cloud Console.
  • Creación de perfiles y planes de consulta:La herramienta de línea de comandos de gcloud y las bibliotecas cliente no son compatibles con la creación de perfiles y los planes de consulta.

Ejemplos

Con el siguiente ejemplo de código, se actualiza la columna MarketingBudget de la tabla Albums.

C#

Usas el método ExecutePartitionedUpdateAsync() para ejecutar una declaración DML particionada.

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

Usas el método PartitionedUpdate() para ejecutar una declaración DML particionada.


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

Usas el método executePartitionedUpdate() para ejecutar una declaración DML particionada.

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

Usas el método runPartitionedUpdate() para ejecutar una declaración DML particionada.

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

Usas el método executePartitionedUpdate() para ejecutar una declaración DML particionada.

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

Usas el método execute_partitioned_dml() para ejecutar una declaración DML particionada.

# 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

Usas el método execute_partitioned_update() para ejecutar una declaración DML particionada.

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

Con el siguiente ejemplo de código, se borran filas de la tabla Singers en función de la columna SingerId.

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