Prácticas recomendadas para el lenguaje de manipulación de datos

En esta página, se describen las prácticas recomendadas para usar el lenguaje de manipulación de datos (DML) y el DML particionado para las bases de datos de dialecto de GoogleSQL y PostgreSQL.

Usa una cláusula WHERE para reducir el alcance de los bloqueos

Las sentencias DML se ejecutan dentro de transacciones de lectura y escritura. Cuando Spanner lee datos, adquiere bloqueos de lectura compartidos en partes limitadas de los rangos de filas que lees. En particular, adquiere estos bloqueos solo en las columnas a las que accedes. Los bloqueos pueden incluir datos que no satisfagan la condición de filtro de la cláusula WHERE.

Cuando Spanner modifica datos con sentencias DML, adquiere bloqueos exclusivos en los datos específicos que estás modificando. Además, adquiere bloqueos compartidos de la misma manera que cuando lees datos. Si tu solicitud incluye rangos de filas grandes o una tabla completa, los bloqueos compartidos podrían impedir que otras transacciones progresen en paralelo.

Para modificar los datos de la manera más eficiente posible, usa una cláusula WHERE que permita que Spanner lea solo las filas necesarias. Puedes lograr este objetivo con un filtro en la clave primaria o en la clave de un índice secundario. La cláusula WHERE limita el alcance de los bloqueos compartidos y permite que Spanner procese la actualización de manera más eficiente.

Por ejemplo, supongamos que uno de los músicos de la tabla Singers cambia su nombre y tienes que actualizarlo en tu base de datos. Podrías ejecutar la siguiente sentencia DML, pero obliga a Spanner a analizar toda la tabla y adquiere bloqueos compartidos que cubren toda la tabla. Como resultado, Spanner debe leer más datos de los necesarios, y las transacciones simultáneas no pueden modificar los datos en paralelo:

-- ANTI-PATTERN: SENDING AN UPDATE WITHOUT THE PRIMARY KEY COLUMN
-- IN THE WHERE CLAUSE

UPDATE Singers SET FirstName = "Marcel"
WHERE FirstName = "Marc" AND LastName = "Richards";

Para que la actualización sea más eficiente, incluye la columna SingerId en la cláusula WHERE. La columna SingerId es la única columna de clave primaria de la tabla Singers:

-- ANTI-PATTERN: SENDING AN UPDATE THAT MUST SCAN THE ENTIRE TABLE

UPDATE Singers SET FirstName = "Marcel"
WHERE FirstName = "Marc" AND LastName = "Richards"

Si no hay un índice en FirstName o LastName, debes analizar toda la tabla para encontrar los cantantes de destino. Si no deseas agregar un índice secundario para que la actualización sea más eficiente, incluye la columna SingerId en la cláusula WHERE.

La columna SingerId es la única columna de clave primaria para la tabla Singers. Para encontrarlo, ejecuta SELECT en una transacción de solo lectura independiente antes de la transacción de actualización:


  SELECT SingerId
  FROM Singers
  WHERE FirstName = "Marc" AND LastName = "Richards"

  -- Recommended: Including a seekable filter in the where clause

  UPDATE Singers SET FirstName = "Marcel"
  WHERE SingerId = 1;

Evita usar sentencias DML y mutaciones en la misma transacción

Spanner almacena en búfer las inserciones, actualizaciones y eliminaciones que se realizan con sentencias DML en el servidor, y los resultados son visibles para las sentencias SQL y DML posteriores dentro de la misma transacción. Este comportamiento es diferente del de la API de Mutation, en la que Spanner almacena en búfer las mutaciones del lado del cliente y las envía del lado del servidor como parte de la operación de confirmación. Como resultado, las mutaciones en la solicitud de confirmación no son visibles para las sentencias de SQL o DML dentro de la misma transacción.

Evita usar sentencias DML y mutaciones en la misma transacción. Si usas ambas en la misma transacción, debes tener en cuenta el orden de ejecución en el código de la biblioteca cliente. Si una transacción contiene sentencias DML y mutaciones en la misma solicitud, Spanner ejecuta las sentencias DML antes de las mutaciones.

Si usas ambas, el búfer escribe solo al final de la transacción.

Usa la función PENDING_COMMIT_TIMESTAMP para escribir marcas de tiempo de confirmación

GoogleSQL

Usa la función PENDING_COMMIT_TIMESTAMP para escribir la marca de tiempo de confirmación en una declaración DML. Spanner selecciona la marca de tiempo de confirmación cuando se confirma la transacción.

PostgreSQL

Usa la función SPANNER.PENDING_COMMIT_TIMESTAMP() para escribir la marca de tiempo de confirmación en una declaración DML. Spanner selecciona la marca de tiempo de confirmación cuando se confirma la transacción.

DML particionado y funciones de fecha y marca de tiempo

El DML particionado usa una o más transacciones que pueden ejecutarse y confirmarse en momentos diferentes. Si usas las funciones date o timestamp, las filas modificadas pueden contener valores diferentes.

Mejora la latencia con DML por lotes

Para reducir la latencia, usa DML por lotes para enviar varias sentencias DML a Spanner en un solo recorrido de ida y vuelta entre el cliente y el servidor.

La DML por lotes puede aplicar optimizaciones a grupos de sentencias dentro de un lote para permitir actualizaciones de datos más rápidas y eficientes.

  • Ejecuta escrituras con una sola solicitud

    Spanner optimiza automáticamente los grupos contiguos de sentencias INSERT, UPDATE o DELETE similares que tienen diferentes valores de parámetros, si no infringen las dependencias de datos.

    Por ejemplo, imagina una situación en la que deseas insertar un conjunto grande de filas nuevas en una tabla llamada Albums. Para permitir que Spanner optimice todas las sentencias INSERT requeridas en una sola acción eficiente del lado del servidor, comienza por escribir una declaración DML adecuada que use parámetros de consulta en SQL:

    INSERT INTO Albums (SingerId, AlbumId, AlbumTitle) VALUES (@Singer, @Album, @Title);
    

    Luego, envía a Spanner un lote de DML que invoque esta sentencia de forma repetida y contigua, con las repeticiones que difieren solo en los valores que vinculas a los tres parámetros de consulta de la sentencia. Spanner optimiza estas sentencias DML estructuralmente idénticas en una sola operación del servidor antes de ejecutarla.

  • Ejecuta escrituras en paralelo

    Spanner optimiza automáticamente los grupos contiguos de sentencias DML ejecutándolos en paralelo cuando esto no infringe las dependencias de datos. Esta optimización aporta beneficios de rendimiento a un conjunto más amplio de sentencias DML por lotes, ya que se puede aplicar a una combinación de tipos de declaración DML (INSERT, UPDATE y DELETE) y a sentencias DML parametrizadas o no parametrizadas.

    Por ejemplo, nuestro esquema de muestra tiene las tablas Singers, Albums y Accounts. Albums se intercala en Singers y almacena información sobre los álbumes de Singers. El siguiente grupo contiguo de sentencias escribe filas nuevas en varias tablas y no tiene dependencias de datos complejas.

    INSERT INTO Singers (SingerId, Name) VALUES(1, "John Doe");
    INSERT INTO Singers (SingerId, Name) VALUES(2, "Marcel Richards");
    INSERT INTO Albums(SingerId, AlbumId, AlbumTitle) VALUES (1, 10001, "Album 1");
    INSERT INTO Albums(SingerId, AlbumId, AlbumTitle) VALUES (1, 10002, "Album 2");
    INSERT INTO Albums(SingerId, AlbumId, AlbumTitle) VALUES (2, 10001, "Album 1");
    UPDATE Accounts SET Balance = 100 WHERE AccountId = @AccountId;
    

    Spanner optimiza este grupo de sentencias DML ejecutando las sentencias en paralelo. Las escrituras se aplican en el orden de las declaraciones en el lote y se mantiene la semántica de DML por lotes si una declaración falla durante la ejecución.

Habilita el procesamiento por lotes del cliente en JDBC

En el caso de las aplicaciones de Java que usan un controlador de JDBC compatible con Spanner, puedes reducir la latencia habilitando el procesamiento por lotes de DML del cliente. El controlador de JDBC tiene una propiedad de conexión llamada auto_batch_dml que, cuando está habilitada, almacena en búfer las sentencias DML en el cliente y las envía a Spanner como un solo lote. Esto puede reducir la cantidad de viajes de ida y vuelta al servidor y mejorar el rendimiento general.

De forma predeterminada, auto_batch_dml se configura como false. Puedes habilitarlo si lo configuras como true en tu cadena de conexión de JDBC.

Por ejemplo:

String url = "jdbc:cloudspanner:/projects/my-project/instances/my-instance/databases/my-database;auto_batch_dml=true";
try (Connection connection = DriverManager.getConnection(url)) {
    // Include your DML statements for batching here
}

Con esta propiedad de conexión habilitada, Spanner envía sentencias DML almacenadas en búfer como un lote cuando se ejecuta una sentencia que no es DML o cuando se confirma la transacción actual. Esta propiedad solo se aplica a las transacciones de lectura y escritura. Las sentencias DML en modo de confirmación automática se ejecutan directamente.

De forma predeterminada, el recuento de actualizaciones para las sentencias DML almacenadas en el búfer se establece en 1. Puedes cambiar este comportamiento si configuras la variable de conexión auto_batch_dml_update_count con un valor diferente. Para obtener más información, consulta Propiedades de conexión compatibles con JDBC.

Usa la opción last_statement para reducir la latencia de DML

Cuando la última sentencia de una transacción de lectura y escritura es una declaración DML, puedes usar la opción de consulta last_statement para reducir la latencia. Esta opción está disponible en las APIs de consulta executeSql y executeStreamingSql.

Con esta opción, se aplazan algunos pasos de validación, como la validación de restricciones únicas, hasta que se confirma la transacción. Cuando se usa last_statement, se rechazan las operaciones posteriores, como lecturas, consultas y DML, en la misma transacción. Esta opción no es compatible con las mutaciones. Si incluyes mutaciones en la misma transacción, Spanner devuelve un error.

La opción last_statement es compatible con las siguientes bibliotecas cliente:

  • Go en la versión 1.77.0 o posterior
  • Java en la versión 2.27.0 o posterior
  • Python en la versión 3.53.0 o posterior
  • PGAdapter en la versión 0.45.0 o posterior

Se admite y habilita de forma predeterminada cuando se usa el modo de confirmación automática en los siguientes controladores:

Go

GoogleSQL

import (
    "context"
    "fmt"
    "io"

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

// Updates a row while also setting the update DML as the last
// statement.
func updateDmlWithLastStatement(w io.Writer, db string) error {
    ctx := context.Background()
    client, err := spanner.NewClient(ctx, db)
    if err != nil {
        return err
    }
    defer client.Close()

    _, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
        // other statements for the transaction if any.

        updateStmt := spanner.Statement{
            SQL: `UPDATE Singers SET LastName = 'Doe' WHERE SingerId = 54213`,
        }
        opts := spanner.QueryOptions{LastStatement: true}
        updateRowCount, err := txn.UpdateWithOptions(ctx, updateStmt, opts)
        if err != nil {
            return err
        }
        fmt.Fprintf(w, "%d record(s) updated.\n", updateRowCount)
        return nil
    })
    if err != nil {
        return err
    }

    return nil
}

PostgreSQL

import (
    "context"
    "fmt"
    "io"

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

// Updates a row while also setting the update DML as the last
// statement.
func pgUpdateDmlWithLastStatement(w io.Writer, db string) error {
    ctx := context.Background()
    client, err := spanner.NewClient(ctx, db)
    if err != nil {
        return err
    }
    defer client.Close()

    _, err = client.ReadWriteTransaction(ctx, func(ctx context.Context, txn *spanner.ReadWriteTransaction) error {
        // other statements for the transaction if any.

        updateStmt := spanner.Statement{
            SQL: `UPDATE Singers SET LastName = 'Doe' WHERE SingerId = 54214`,
        }
        opts := spanner.QueryOptions{LastStatement: true}
        updateRowCount, err := txn.UpdateWithOptions(ctx, updateStmt, opts)
        if err != nil {
            return err
        }
        fmt.Fprintf(w, "%d record(s) updated.\n", updateRowCount)
        return nil
    })
    if err != nil {
        return err
    }

    return nil
}

Java

GoogleSQL

static void UpdateUsingLastStatement(DatabaseClient client) {
    client
        .readWriteTransaction()
        .run(
            transaction -> {
            // other statements for the transaction if any

            // Pass in the `lastStatement` option to the last DML statement of the transaction.
            transaction.executeUpdate(
                Statement.of(
                    "UPDATE Singers SET Singers.LastName = 'Doe' WHERE SingerId = 54213\n"),
                Options.lastStatement());
            System.out.println("Singer last name updated.");

            return null;
            });
}

PostgreSQL

static void UpdateUsingLastStatement(DatabaseClient client) {
    client
        .readWriteTransaction()
        .run(
            transaction -> {
            // other statements for the transaction if any.

            // Pass in the `lastStatement` option to the last DML statement of the transaction.
            transaction.executeUpdate(
                Statement.of("UPDATE Singers SET LastName = 'Doe' WHERE SingerId = 54214\n"),
                Options.lastStatement());
            System.out.println("Singer last name updated.");

            return null;
            });
}

Python

GoogleSQL

def dml_last_statement_option(instance_id, database_id):
"""Updates using DML where the update set the last statement option."""
# [START spanner_dml_last_statement]
# 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)

def update_singers(transaction):
    # other statements for the transaction if any.

    update_row_ct = transaction.execute_update(
        "UPDATE Singers SET LastName = 'Doe' WHERE SingerId = 54213",
        last_statement=True)

    print("{} record(s) updated.".format(update_row_ct))

database.run_in_transaction(update_singers)

PostgreSQL

def dml_last_statement_option(instance_id, database_id):
"""Updates using DML where the update set the last statement option."""
# 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)

def update_singers(transaction):
    # other statements for the transaction if any.

    update_row_ct = transaction.execute_update(
        "UPDATE Singers SET LastName = 'Doe' WHERE SingerId = 54214",
        last_statement=True)

    print("{} record(s) updated.".format(update_row_ct))

database.run_in_transaction(update_singers)