Esta página descreve as práticas recomendadas para usar a linguagem de manipulação de dados (DML) e a DML particionada para bases de dados com dialeto GoogleSQL e bases de dados com dialeto PostgreSQL.
Use uma cláusula WHERE
para reduzir o âmbito dos bloqueios
Executa declarações DML em transações de leitura/escrita. Quando o Spanner lê dados, adquire bloqueios de leitura partilhados em porções limitadas dos intervalos de linhas que lê. Especificamente, adquire estes bloqueios apenas nas colunas às quais acede. Os bloqueios podem incluir dados que não
satisfazem a condição de filtro da cláusula WHERE
.
Quando o Spanner modifica dados através de declarações DML, adquire bloqueios exclusivos nos dados específicos que está a modificar. Além disso, adquire bloqueios partilhados da mesma forma que quando lê dados. Se o seu pedido incluir grandes intervalos de linhas ou uma tabela inteira, os bloqueios partilhados podem impedir que outras transações progridam em paralelo.
Para modificar os dados da forma mais eficiente possível, use uma cláusula WHERE
que permita ao Spanner ler apenas as linhas necessárias. Pode atingir este objetivo com um filtro na chave principal ou na chave de um índice secundário. A cláusula WHERE
limita o âmbito dos bloqueios partilhados e permite que o Spanner processe a atualização de forma mais eficiente.
Por exemplo, suponhamos que um dos músicos na tabela Singers
altera o nome próprio e tem de atualizar o nome na sua base de dados. Pode executar a seguinte declaração DML, mas força o Spanner a analisar toda a tabela e adquire bloqueios partilhados que abrangem toda a tabela. Como resultado, o Spanner tem de ler mais dados do que o necessário e as transações simultâneas não podem modificar os dados em 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 tornar a atualização mais eficiente, inclua a coluna SingerId
na cláusula WHERE
. A coluna SingerId
é a única coluna de chave principal para a tabela Singers
:
-- ANTI-PATTERN: SENDING AN UPDATE THAT MUST SCAN THE ENTIRE TABLE
UPDATE Singers SET FirstName = "Marcel"
WHERE FirstName = "Marc" AND LastName = "Richards"
Se não existir um índice em FirstName
ou LastName
, tem de
analisar toda a tabela para encontrar os cantores-alvo. Se não quiser adicionar um índice secundário para tornar a atualização mais eficiente, inclua a coluna SingerId
na cláusula WHERE
.
A coluna SingerId
é a única coluna de chave principal para a tabela Singers
. Para o encontrar, execute SELECT
numa transação separada de leitura antes da transação de atualização:
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;
Evite usar declarações DML e mutações na mesma transação
O Spanner armazena em buffer as inserções, as atualizações e as eliminações realizadas através de declarações DML no lado do servidor, e os resultados são visíveis para declarações SQL e DML subsequentes na mesma transação. Este comportamento é diferente da API Mutation, em que o Spanner armazena em buffer as mutações do lado do cliente e envia as mutações do lado do servidor como parte da operação de confirmação. Como resultado, as mutações no pedido de confirmação não são visíveis para as declarações SQL ou DML na mesma transação.
Evite usar declarações DML e mutações na mesma transação. Se usar ambos na mesma transação, tem de ter em conta a ordem de execução no código da biblioteca de cliente. Se uma transação contiver declarações DML e mutações no mesmo pedido, o Spanner executa as declarações DML antes das mutações.
Para operações que só são suportadas através de mutações, é recomendável
combinar declarações DML e mutações na mesma transação, por exemplo,
insert_or_update
.
Se usar ambas, o buffer escreve apenas no final da transação.
Use a função PENDING_COMMIT_TIMESTAMP
para escrever as datas/horas de confirmação
GoogleSQL
Use a função PENDING_COMMIT_TIMESTAMP
para escrever a data/hora de confirmação numa declaração DML. O Spanner seleciona a data/hora de confirmação quando a transação é confirmada.
PostgreSQL
Use a função SPANNER.PENDING_COMMIT_TIMESTAMP()
para escrever a data/hora de confirmação numa declaração DML. O Spanner seleciona a data/hora de confirmação quando a transação é confirmada.
DML particionada e funções de data e hora
A DML particionada usa uma ou mais transações que podem ser executadas e confirmadas em momentos diferentes. Se usar as funções date ou timestamp, as linhas modificadas podem conter valores diferentes.
Melhore a latência com DML em lote
Para reduzir a latência, use o DML em lote para enviar várias declarações DML para o Spanner numa única viagem de ida e volta cliente-servidor.
O DML em lote pode aplicar otimizações a grupos de declarações num lote para permitir atualizações de dados mais rápidas e eficientes.
Execute escritas com um único pedido
O Spanner otimiza automaticamente grupos contíguos de declarações em lote semelhantes de
INSERT
,UPDATE
ouDELETE
que tenham valores de parâmetros diferentes, se não violarem as dependências de dados.Por exemplo, considere um cenário em que quer inserir um grande conjunto de novas linhas numa tabela denominada
Albums
. Para permitir que o Spanner otimize todas as declaraçõesINSERT
necessárias numa única ação do lado do servidor eficiente, comece por escrever uma declaração DML adequada que use parâmetros de consulta SQL:INSERT INTO Albums (SingerId, AlbumId, AlbumTitle) VALUES (@Singer, @Album, @Title);
Em seguida, envie ao Spanner um lote de DML que invoque esta declaração repetidamente e de forma contígua, com as repetições a diferirem apenas nos valores que associa aos três parâmetros de consulta da declaração. O Spanner otimiza estas declarações de DML estruturalmente idênticas numa única operação do lado do servidor antes de a executar.
Execute escritas em paralelo
O Spanner otimiza automaticamente grupos contíguos de declarações DML executando-os em paralelo quando isso não viola as dependências de dados. Esta otimização traz vantagens de desempenho a um conjunto mais amplo de instruções DML em lote, porque pode aplicar-se a uma combinação de tipos de instruções DML (
INSERT
,UPDATE
eDELETE
) e a instruções DML parametrizadas ou não parametrizadas.Por exemplo, o nosso esquema de amostra tem as tabelas
Singers
,Albums
eAccounts
.Albums
está intercalado emSingers
e armazena informações sobre álbuns paraSingers
. O seguinte grupo contíguo de declarações escreve novas linhas em várias tabelas e não tem dependências de dados complexas.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;
O Spanner otimiza este grupo de declarações DML executando-as em paralelo. As escritas são aplicadas por ordem das declarações no lote e mantêm a semântica DML do lote se uma declaração falhar durante a execução.
Ative o processamento em lote do lado do cliente no JDBC
Para aplicações Java que usam um controlador JDBC suportado pelo Spanner, pode reduzir a latência ativando o processamento em lote de DML do lado do cliente. O controlador JDBC tem uma propriedade de ligação denominada auto_batch_dml
que, quando ativada, armazena em buffer as declarações DML no cliente e envia-as para o Spanner como um único lote. Isto pode reduzir o número de viagens de ida e volta ao servidor e melhorar o desempenho geral.
Por predefinição, auto_batch_dml
está definido como false
. Pode ativá-lo definindo-o como true
na string de ligação JDBC.
Por exemplo:
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
}
Com esta propriedade de ligação ativada, o Spanner envia declarações DML em buffer como um lote quando é executada uma declaração não DML ou quando a transação atual é confirmada. Esta propriedade aplica-se apenas a transações de leitura/escrita; as declarações DML no modo de confirmação automática são executadas diretamente.
Por predefinição, a contagem de atualizações para declarações DML em buffer está definida como 1
. Pode
alterar esta definição definindo a variável de ligação auto_batch_dml_update_count
para
um valor diferente. Para mais informações, consulte o artigo Propriedades de ligação suportadas pelo JDBC.
Use a opção last_statement
para reduzir a latência de DML
Quando a última declaração numa transação de leitura/escrita é uma declaração DML, pode usar a opção de consulta last_statement
para reduzir a latência. Esta opção está
disponível nas APIs de consulta
executeSql
e executeStreamingSql
.
A utilização desta opção adia alguns passos de validação, como a validação de restrições únicas, até a transação ser confirmada. Quando usa last_statement
, as operações subsequentes, como leituras, consultas e DML, na mesma transação são rejeitadas. Esta opção não é compatível com mutações. Se incluir mutações na mesma transação, o Spanner devolve um erro.
A opção last_statement
é suportada nas seguintes bibliotecas cliente:
- Go na versão 1.77.0 ou posterior
- Java na versão 2.27.0 ou posterior
- Python na versão 3.53.0 ou posterior
- PGAdapter na versão 0.45.0 ou posterior
É suportado e ativado por predefinição quando usa o modo de confirmação automática nos seguintes controladores:
- Driver JDBC na versão 6.87.0 ou posterior
- Go database/sql driver na versão 1.11.2 ou posterior
Driver Python dbapi na versão 3.53.0 ou posterior
Ir
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)