Linguaggio di manipolazione dati partizionati

Il partito di manipolazione dei dati partizionato (DML) è progettato per i seguenti tipi di aggiornamenti ed eliminazioni collettive:

  • Pulizia periodica e raccolta dei rifiuti. Ad esempio, le righe precedenti o le colonne impostate su NULL vengono eliminate.
  • Backfill delle nuove colonne con valori predefiniti. Un esempio è utilizzare un'istruzione UPDATE per impostare un nuovo valore della colonna su False dove attualmente è NULL.

DML e DML partizionato

Spanner supporta due modalità di esecuzione per le istruzioni DML:

  • DML, che è adatto all'elaborazione di transazioni. Per ulteriori informazioni, consulta la sezione Utilizzo di DML.

  • DML partizionato, che consente operazioni su larga scala a livello di database con un impatto minimo sull'elaborazione contemporanea delle transazioni partizionando lo spazio delle chiavi ed eseguendo l'istruzione sulle partizioni in transazioni separate con ambito più limitato. Per ulteriori informazioni, consulta la sezione Utilizzare il DML partizionato.

La tabella seguente evidenzia alcune delle differenze tra le due modalità di esecuzione.

DML DML partizionato
Le righe che non corrispondono alla clausola WHERE potrebbero essere bloccate. Vengono bloccate solo le righe che corrispondono alla clausola WHERE.
Si applicano limiti per le dimensioni delle transazioni. Spanner gestisce i limiti delle transazioni e quelli della contemporaneità per transazione.
Non è necessario che le dichiarazioni siano idempotenti. Per garantire risultati coerenti, un'istruzione DML deve essere idempotente.
Una transazione può includere più istruzioni DML e SQL. Una transazione partizionata può includere solo un'istruzione DML.
Non ci sono limitazioni alla complessità delle dichiarazioni. Le istruzioni devono essere completamente partibili.
Le transazioni di lettura-scrittura vengono create nel codice client. Spanner crea le transazioni.

Partizionabili e idempotenti

Quando viene eseguita un'istruzione DML partizionata, le righe in una partizione non hanno accesso alle righe in altre partizioni e non puoi scegliere come Spanner crea le partizioni. Il partizionamento garantisce la scalabilità, ma significa anche che le istruzioni DML partizionate devono essere completamente partibili. Ciò significa che l'istruzione DML partizionata deve essere esplicitabile come l'unione di un set di istruzioni, in cui ogni istruzione accede a una singola riga della tabella e ogni istruzione non accede ad altre tabelle. Ad esempio, un'istruzione DML che accede a più tabelle o esegue un'unione autonoma non è condivisibile. Se l'istruzione DML non è partizionabile, Spanner restituisce l'errore BadUsage.

Queste istruzioni DML sono completamente suddivisibili, perché ogni istruzione può essere applicata a una singola riga della tabella:

UPDATE Singers SET LastName = NULL WHERE LastName = '';

DELETE FROM Albums WHERE MarketingBudget > 10000;

Questa istruzione DML non è completamente partizionabile perché accede a più tabelle:

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

Spanner potrebbe eseguire più volte un'istruzione DML partizionata contro alcune partizioni a causa di nuovi tentativi a livello di rete. Di conseguenza, un'istruzione potrebbe essere eseguita più di una volta in una riga. Pertanto, l'affermazione deve essere idempotente per ottenere risultati coerenti. Un'istruzione è idempotente se l'esecuzione più volte in una singola riga porta allo stesso risultato.

Questa istruzione DML è idempotente:

UPDATE Singers SET MarketingBudget = 1000 WHERE true;

Questa istruzione DML non è idempotente:

UPDATE Singers SET MarketingBudget = 1.5 * MarketingBudget WHERE true;

Blocco delle righe

Spanner acquisisce un blocco solo se una riga è idonea all'aggiornamento o all'eliminazione. Questo comportamento è diverso dall'esecuzione di DML, che potrebbe righe di blocco lettura che non corrispondono alla clausola WHERE.

Esecuzione e transazioni

Se un'istruzione DML è partizionata o meno dipende dal metodo di libreria client che hai scelto per l'esecuzione. Ogni libreria client fornisce metodi separati per l'esecuzione DML e l'esecuzione DML partizionata.

Puoi eseguire una sola istruzione DML partizionata in una chiamata al metodo libreria client.

Spanner non applica le istruzioni DML partizionate in modo atomico all'intera tabella. Spanner, tuttavia, applica le istruzioni DML partizionate in modo atomico su ogni partizione.

DML partizionato non supporta il commit o il rollback. Spanner viene eseguito e applica immediatamente l'istruzione DML.

  • Se annulli l'operazione, Spanner annulla le partizioni in esecuzione e non avvia le partizioni rimanenti. Spanner non esegue il rollback di tutte le partizioni già eseguite.
  • Se l'esecuzione dell'istruzione causa un errore, l'esecuzione si interrompe in tutte le partizioni e Spanner restituisce l'errore per l'intera operazione. Alcuni esempi di errori sono violazioni dei vincoli dei tipi di dati, violazioni dell'elemento UNIQUE INDEX e violazioni dell'elemento ON DELETE NO ACTION. A seconda del momento in cui l'esecuzione non è riuscita, l'istruzione potrebbe essere stata eseguita correttamente su alcune partizioni e potrebbe non essere mai stata eseguita su altre partizioni.

Se l'istruzione DML partizionata ha esito positivo, Spanner ha eseguito l'istruzione almeno una volta in base a ciascuna partizione dell'intervallo di chiavi.

Conteggio delle righe modificate

Un'istruzione DML partizionata restituisce un limite inferiore nel numero di righe modificate. Potrebbe non essere un conteggio esatto del numero di righe modificate, perché non vi è alcuna garanzia che Spanner conteggi tutte le righe modificate.

Limiti transazioni

Spanner crea le partizioni e le transazioni di cui ha bisogno per eseguire un'istruzione DML partizionata. Si applicano limiti per le transazioni o per contemporaneità di transazione, ma Spanner tenta di mantenere le transazioni entro i limiti.

Spanner consente un massimo di 20.000 istruzioni DML partizionate in contemporanea per database.

Funzionalità non supportate

Spanner non supporta alcune funzionalità per DML partizionato:

  • La lingua INSERT non è supportata.
  • console: non puoi eseguire istruzioni DML partizionate nella console.
  • Piani di query e profilazione: l'interfaccia a riga di comando di Google Cloud e le librerie client non supportano i piani di query e la profilazione.
  • Sottoquery che leggono un'altra tabella o una riga diversa della stessa tabella.

In scenari complessi, come lo spostamento di una tabella o trasformazioni che richiedono unione tra tabelle, puoi utilizzare il connettore Dataflow.

Esempi

Il seguente esempio di codice aggiorna la colonna MarketingBudget della tabella Albums.

C++

La funzione ExecutePartitionedDml() serve per eseguire un'istruzione DML partizionata.

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#

Utilizza il metodo ExecutePartitionedUpdateAsync() per eseguire un'istruzione DML partizionata.


using Google.Cloud.Spanner.Data;
using System;
using System.Threading.Tasks;

public class UpdateUsingPartitionedDmlCoreAsyncSample
{
    public async Task<long> UpdateUsingPartitionedDmlCoreAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        using var connection = new SpannerConnection(connectionString);
        await connection.OpenAsync();

        using var cmd = connection.CreateDmlCommand("UPDATE Albums SET MarketingBudget = 100000 WHERE SingerId > 1");
        long rowCount = await cmd.ExecutePartitionedUpdateAsync();

        Console.WriteLine($"{rowCount} row(s) updated...");
        return rowCount;
    }
}

Vai

Utilizza il metodo PartitionedUpdate() per eseguire un'istruzione DML partizionata.


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

Utilizza il metodo executePartitionedUpdate() per eseguire un'istruzione DML partizionata.

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

Utilizza il metodo runPartitionedUpdate() per eseguire un'istruzione DML partizionata.

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

Utilizza il metodo executePartitionedUpdate() per eseguire un'istruzione DML partizionata.

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

Utilizza il metodo execute_partitioned_dml() per eseguire un'istruzione DML partizionata.

# 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

Utilizza il metodo execute_partitioned_update() per eseguire un'istruzione DML partizionata.

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

Il seguente esempio di codice elimina le righe dalla tabella Singers, in base alla colonna 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#


using Google.Cloud.Spanner.Data;
using System;
using System.Threading.Tasks;

public class DeleteUsingPartitionedDmlCoreAsyncSample
{
    public async Task<long> DeleteUsingPartitionedDmlCoreAsync(string projectId, string instanceId, string databaseId)
    {
        string connectionString = $"Data Source=projects/{projectId}/instances/{instanceId}/databases/{databaseId}";

        using var connection = new SpannerConnection(connectionString);
        await connection.OpenAsync();

        using var cmd = connection.CreateDmlCommand("DELETE FROM Singers WHERE SingerId > 10");
        long rowCount = await cmd.ExecutePartitionedUpdateAsync();

        Console.WriteLine($"{rowCount} row(s) deleted...");
        return rowCount;
    }
}

Vai


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

Passaggi successivi