Linguagem de manipulação de dados particionada

Linguagem de manipulação de dados particionada DML) foi desenvolvido para os seguintes tipos de atualizações e exclusões em massa:

  • Limpeza periódica e coleta de lixo. Alguns exemplos são excluir linhas antigas ou definir colunas como NULL.
  • Preenchimento de novas colunas com valores padrão. Um exemplo é o uso de uma instrução UPDATE para definir o valor de uma nova coluna como False, no qual o valor atual é NULL.

A DML particionada não é adequada para processamento de transações em pequena escala. Se você executar uma instrução em poucas linhas, usar DMLs transacionais com informações chaves primárias. Para mais informações, consulte Como usar a DML.

Se você precisar executar commit de um grande número de gravações cegas sem precisar atômica, é possível modificar em massa as tabelas do Spanner usando gravação em lote. Para mais informações, consulte Modificar dados usando gravações em lote.

Você pode receber insights sobre consultas DML particionadas ativas e o progresso delas nas tabelas de estatísticas no seu banco de dados do Spanner. Para mais informações, consulte Estatísticas de DMLs particionadas ativas.

DML e DML particionada

O Spanner é compatível com dois modos de execução para instruções DML:

  • DML, que é adequada para o processamento de transações. Para mais informações, consulte Como usar a DML.

  • DML particionada, que permite operações em larga escala em todo o banco de dados com impacto mínimo no processamento de transações simultâneas, particionando o espaço de chave e executando a instrução em partições em transações separadas, com escopo menor. Para mais informações, consulte Como usar a DML particionada.

A tabela a seguir destaca algumas das diferenças entre os dois modos de execução.

DML DML particionada
As linhas que não correspondem à cláusula WHERE podem ser bloqueadas. Apenas as linhas que correspondem à cláusula WHERE são bloqueadas.
Limites de tamanho de transação são aplicáveis. O Spanner gerencia os limites de transação e os limites de simultaneidade por transação.
As instruções não precisam ser idempotentes. Uma instrução DML precisa ser idempotente para garantir resultados consistentes.
Uma transação pode incluir várias declarações DML e instruções SQL. Uma transação particionada pode incluir apenas uma declaração DML.
Não há restrições à complexidade das declarações. As declarações precisam ser totalmente particionáveis.
Você cria transações de leitura/gravação no seu código de cliente. O Spanner cria as transações.

Particionável e idempotente

Quando uma instrução DML particionada é executada, as linhas em uma partição não têm acesso às linhas em outras partições, e não é possível escolher como o Spanner cria as partições. O particionamento garante a escalonabilidade, mas também significa que as instruções DML particionadas precisam ser totalmente particionáveis. Ou seja, uma instrução DML particionada precisa ser expressa como a união de um conjunto de instruções, em que cada instrução acessa uma única linha da tabela e cada não acessa outras tabelas. Por exemplo, uma instrução DML que acessa várias tabelas ou realiza uma união automática não é particionável. Se a instrução DML não puder ser particionada, o Spanner retornará o erro BadUsage.

Estas instruções DML são totalmente particionáveis porque cada uma pode ser aplicada a uma única linha na tabela:

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

DELETE FROM Albums WHERE MarketingBudget > 10000;

Esta instrução DML não é totalmente particionável, porque acessa várias tabelas:

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

O Spanner pode executar uma instrução DML particionada várias vezes em algumas partições devido a novas tentativas no nível da rede. Como resultado, uma instrução pode ser executado mais de uma vez em uma linha. A instrução precisa, portanto, ser idempotente para produzir resultados consistentes. Uma instrução é idempotente se executá-la várias vezes em uma única linha levar ao mesmo resultado.

Esta instrução DML é idempotente:

UPDATE Singers SET MarketingBudget = 1000 WHERE true;

Esta não é:

UPDATE Singers SET MarketingBudget = 1.5 * MarketingBudget WHERE true;

Bloqueio de linhas

O Spanner adquire um bloqueio somente se uma linha for candidata a atualização ou exclusão. Esse comportamento é diferente Execução da DML, que pode bloquear a leitura linhas que não correspondem à cláusula WHERE.

Execução e transações

Se uma declaração DML é particionada ou não, depende do método da biblioteca de cliente que você escolhe para execução. Cada biblioteca de cliente fornece métodos separados para execução de DML e execução de DML particionada.

Só é possível executar uma instrução DML particionada em uma chamada para o cliente método da biblioteca.

O Spanner não aplica as declarações DML particionadas atomicamente em toda a tabela. No entanto, o Spanner aplica declarações DML particionadas atomicamente em cada partição.

A DML particionada não é compatível com consolidação ou reversão. O Spanner executa e aplica a instrução DML imediatamente.

  • Se você cancelar a operação, o Spanner cancela as partições em execução e não inicia as partições restantes. O Spanner não reverter nenhuma partição que já executada.
  • Se a execução da instrução causar um erro, ela será interrompida. em todas as partições, e o Spanner retorna esse erro para em toda a operação. Alguns exemplos de erros são violações de restrições de tipo de dados, violações de UNIQUE INDEX e violações de ON DELETE NO ACTION. Dependendo do momento em que a execução falhou, o pode ter sido executada com sucesso em algumas partições e pode nunca foram executados em outras partições.

Se a instrução DML particionada for bem-sucedida, o Spanner executará a pelo menos uma vez em cada partição do intervalo de chaves.

Contagem de linhas modificadas

Uma instrução DML particionada retorna um limite inferior do número de linhas modificadas. Pode não ser uma contagem exata do número de linhas modificadas, porque não há garantia de que o Spanner conta todas as linhas modificadas.

Limites de transação

O Spanner cria as partições e transações necessárias executar uma instrução DML particionada. Limites de transação ou limites de simultaneidade por transação se aplicam, mas o Spanner tenta manter as transações dentro dos limites.

O Spanner permite um máximo de 20.000 declarações DML particionadas simultâneas por banco de dados.

Recursos não suportados

O Spanner não é compatível com alguns recursos para DML particionada:

  • INSERT não é compatível.
  • Console do Google Cloud: não é possível executar instruções DML particionadas no console do Google Cloud.
  • Planos de consulta e criação de perfil: a Google Cloud CLI e o cliente as bibliotecas não aceitam planos de consulta e criação de perfil.
  • Subconsultas que leem de outra tabela ou de uma linha diferente da mesma tabela.

Para cenários complexos, como mover uma tabela ou transformações que exigem junções entre tabelas, considere usar o conector do Dataflow.

Exemplos

O exemplo de código a seguir atualiza a coluna MarketingBudget da tabela Albums.

C++

Use a função ExecutePartitionedDml() para executar uma instrução DML particionada.

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::move(result).status();
  std::cout << "Updated at least " << result->row_count_lower_bound
            << " row(s) [spanner_dml_partitioned_update]\n";
}

C#

Use o método ExecutePartitionedUpdateAsync() para executar uma instrução DML particionada.


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

Go

Use o método PartitionedUpdate() para executar uma instrução DML particionada.


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

Use o método executePartitionedUpdate() para executar uma instrução 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

Use o método runPartitionedUpdate() para executar uma instrução 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

Use o método executePartitionedUpdate() para executar uma instrução 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(string $instanceId, string $databaseId): void
{
    $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

Use o método execute_partitioned_dml() para executar uma instrução 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

Use o método execute_partitioned_update() para executar uma instrução 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."

O exemplo de código a seguir exclui linhas da tabela Singers com base na coluna 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::move(result).status();
  std::cout << "Deleted at least " << result->row_count_lower_bound
            << " row(s) [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;
    }
}

Go


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(string $instanceId, string $databaseId): void
{
    $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."

A seguir