Auf dieser Seite werden Best Practices für die Verwendung der Datenbearbeitungssprache (Data Manipulation Language, DML) und der partitionierten DML für Datenbanken im GoogleSQL-Dialekt und Datenbanken im PostgreSQL-Dialekt beschrieben.
WHERE-Klausel verwenden, um den Umfang von Sperren zu reduzieren
Sie führen DML-Anweisungen in Lese- und Schreibtransaktionen aus. Wenn Spanner Daten liest, werden freigegebene Lesesperren für begrenzte Teile der Zeilenbereiche erworben, die Sie lesen. Insbesondere werden diese Sperren nur für die Spalten übernommen, auf die Sie zugreifen. Die Sperren können Daten enthalten, die die Filterbedingung der WHERE-Klausel nicht erfüllen.
Wenn Spanner Daten mithilfe von DML-Anweisungen ändert, werden exklusive Sperren für die spezifischen Daten erworben, die Sie ändern. Darüber hinaus werden gemeinsame Sperren auf die gleiche Weise wie beim Lesen von Daten angewendet. Wenn Ihre Anfrage große Zeilenbereiche oder eine ganze Tabelle umfasst, können die freigegebenen Sperren verhindern, dass andere Transaktionen parallel ausgeführt werden.
Wenn Sie Daten so effizient wie möglich ändern möchten, verwenden Sie eine WHERE-Klausel, mit der Spanner nur die erforderlichen Zeilen lesen kann. Sie können dafür einen Filter für den Primärschlüssel oder für den Schlüssel eines Sekundärindex nutzen. Die WHERE-Klausel schränkt den Umfang der freigegebenen Sperren ein und ermöglicht es Spanner, die Aktualisierung effizienter zu verarbeiten.
Angenommen, einer der Musiker in der Tabelle Singers ändert seinen Vornamen und Sie müssen den Namen in Ihrer Datenbank aktualisieren. Sie könnten die folgende DML-Anweisung ausführen, aber dadurch wird Spanner gezwungen, die gesamte Tabelle zu scannen, und es werden freigegebene Sperren erworben, die die gesamte Tabelle abdecken. Daher muss Spanner mehr Daten als nötig lesen und gleichzeitige Transaktionen können die Daten nicht parallel ändern:
-- 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";
Für eine effizientere Aktualisierung nehmen Sie die Spalte SingerId in die WHERE-Klausel auf. Die Spalte SingerId ist die einzige Primärschlüsselspalte für die Tabelle Singers:
-- ANTI-PATTERN: SENDING AN UPDATE THAT MUST SCAN THE ENTIRE TABLE
UPDATE Singers SET FirstName = "Marcel"
WHERE FirstName = "Marc" AND LastName = "Richards"
Wenn kein Index für FirstName oder LastName vorhanden ist, müssen Sie die gesamte Tabelle scannen, um die Zielsänger zu finden. Wenn Sie keinen sekundären Index hinzufügen möchten, um die Aktualisierung effizienter zu gestalten, fügen Sie die Spalte SingerId in die WHERE-Klausel ein.
Die Spalte SingerId ist die einzige Primärschlüsselspalte für die Tabelle Singers. Führen Sie dazu vor der Aktualisierungstransaktion SELECT in einer separaten, schreibgeschützten Transaktion aus:
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;
Vermeiden Sie die Verwendung von DML-Anweisungen und Mutationen in derselben Transaktion
Spanner puffert Einfügungen, Aktualisierungen und Löschungen, die mit DML-Anweisungen auf der Serverseite ausgeführt werden. Die Ergebnisse sind für nachfolgende SQL- und DML-Anweisungen innerhalb derselben Transaktion sichtbar. Dieses Verhalten unterscheidet sich von der Mutation API, bei der Spanner die Mutationen auf der Clientseite puffert und die Mutationen im Rahmen des Commit-Vorgangs serverseitig sendet. Daher sind Mutationen in der Commit-Anfrage für SQL- oder DML-Anweisungen innerhalb derselben Transaktion nicht sichtbar.
Vermeiden Sie die Verwendung von DML-Anweisungen und Mutationen in derselben Transaktion. Wenn Sie beide in derselben Transaktion verwenden, müssen Sie die Ausführungsreihenfolge in Ihrem Clientbibliotheks-Code berücksichtigen. Wenn eine Transaktion sowohl DML-Anweisungen als auch Mutationen in derselben Anfrage enthält, führt Spanner die DML-Anweisungen vor den Mutationen aus.
Wenn Sie beide verwenden, schreibt der Puffer nur ganz am Ende der Transaktion.
Funktion PENDING_COMMIT_TIMESTAMP zum Schreiben von Commit-Zeitstempeln verwenden
GoogleSQL
Sie verwenden Sie Funktion PENDING_COMMIT_TIMESTAMP, um den Commit-Zeitstempel in eine DML-Anweisung zu schreiben. Cloud Spanner wählt den Commit-Zeitstempel aus, wenn der Commit der Transaktion durchgeführt wird.
PostgreSQL
Sie verwenden Sie Funktion SPANNER.PENDING_COMMIT_TIMESTAMP(), um den Commit-Zeitstempel in eine DML-Anweisung zu schreiben. Cloud Spanner wählt den Commit-Zeitstempel aus, wenn der Commit der Transaktion durchgeführt wird.
Partitionierte DML-, Datums- und Zeitstempelfunktionen
Partitionierte DML verwendet eine oder mehrere Transaktionen, die möglicherweise zu unterschiedlichen Zeitpunkten ausgeführt und bereitgestellt werden. Wenn Sie die Funktionen date oder timestamp verwenden, enthalten die geänderten Zeilen möglicherweise unterschiedliche Werte.
Latenz mit Batch-DML verbessern
Verwenden Sie Batch-DML, um mehrere DML-Anweisungen innerhalb eines einzigen Client-Server-Roundtrips an Spanner zu senden und so die Latenz zu reduzieren.
Batch-DML kann Optimierungen auf Gruppen von Anweisungen innerhalb eines Batches anwenden, um schnellere und effizientere Datenaktualisierungen zu ermöglichen.
Schreibvorgänge mit einer einzelnen Anfrage ausführen
Spanner optimiert automatisch zusammenhängende Gruppen ähnlicher
INSERT-,UPDATE- oderDELETE-Batchanweisungen mit unterschiedlichen Parameterwerten, sofern sie nicht gegen Datenabhängigkeiten verstoßen.Stellen Sie sich beispielsweise ein Szenario vor, in dem Sie eine große Anzahl neuer Zeilen in eine Tabelle mit dem Namen
Albumseinfügen möchten. Damit Spanner alle erforderlichenINSERT-Anweisungen in einer einzigen, effizienten serverseitigen Aktion optimieren kann, schreiben Sie zuerst eine entsprechende DML-Anweisung, die SQL-Abfrageparameter verwendet:INSERT INTO Albums (SingerId, AlbumId, AlbumTitle) VALUES (@Singer, @Album, @Title);Senden Sie dann einen DML-Batch an Spanner, der diese Anweisung wiederholt und zusammenhängend aufruft. Die Wiederholungen unterscheiden sich nur in den Werten, die Sie an die drei Abfrageparameter der Anweisung binden. Spanner optimiert diese strukturell identischen DML-Anweisungen vor der Ausführung in einen einzigen serverseitigen Vorgang.
Schreibvorgänge parallel ausführen
Spanner optimiert zusammenhängende Gruppen von DML-Anweisungen automatisch, indem sie parallel ausgeführt werden, sofern dadurch keine Datenabhängigkeiten verletzt werden. Diese Optimierung bietet Leistungsvorteile für eine größere Anzahl von im Batch ausgeführten DML-Anweisungen, da sie auf eine Mischung aus DML-Anweisungstypen (
INSERT,UPDATEundDELETE) sowie auf parametrisierte und nicht parametrisierte DML-Anweisungen angewendet werden kann.Unser Beispielschema enthält beispielsweise die Tabellen
Singers,AlbumsundAccounts.Albumsist inSingersverschachtelt und speichert Informationen zu Alben fürSingers. Die folgende zusammenhängende Gruppe von Anweisungen schreibt neue Zeilen in mehrere Tabellen und hat keine komplexen Datenabhängigkeiten.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 optimiert diese Gruppe von DML-Anweisungen, indem die Anweisungen parallel ausgeführt werden. Die Schreibvorgänge werden in der Reihenfolge der Anweisungen im Batch angewendet. Wenn eine Anweisung während der Ausführung fehlschlägt, wird die Batch-DML-Semantik beibehalten.
Clientseitige Batchverarbeitung in JDBC aktivieren
Bei Java-Anwendungen, die einen von Spanner unterstützten JDBC-Treiber verwenden, können Sie die Latenz reduzieren, indem Sie clientseitiges DML-Batching aktivieren. Der JDBC-Treiber hat ein Verbindungsattribut namens auto_batch_dml, das, wenn es aktiviert ist, DML-Anweisungen auf dem Client puffert und als einzelnen Batch an Spanner sendet. Dadurch kann die Anzahl der Roundtrips zum Server reduziert und die Gesamtleistung verbessert werden.
Standardmäßig ist auto_batch_dml auf false festgelegt. Sie können sie aktivieren, indem Sie sie in Ihrem JDBC-Verbindungsstring auf true setzen.
Beispiel:
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
}
Wenn diese Verbindungseigenschaft aktiviert ist, sendet Cloud Spanner gepufferte DML-Anweisungen als Batch, wenn eine Nicht-DML-Anweisung ausgeführt oder die aktuelle Transaktion festgeschrieben wird. Diese Property gilt nur für Lese-/Schreibtransaktionen. DML-Anweisungen im Autocommit-Modus werden direkt ausgeführt.
Standardmäßig ist die Anzahl der Aktualisierungen für gepufferte DML-Anweisungen auf 1 festgelegt. Sie können dies ändern, indem Sie die Verbindungsvariable auto_batch_dml_update_count auf einen anderen Wert festlegen. Weitere Informationen finden Sie unter Von JDBC unterstützte Verbindungseigenschaften.
Option last_statement verwenden, um die DML-Latenz zu reduzieren
Wenn die letzte Anweisung in einer Lese-/Schreibtransaktion eine DML-Anweisung ist, können Sie die Abfrageoption last_statement verwenden, um die Latenz zu reduzieren. Diese Option ist in den Abfrage-APIs executeSql und executeStreamingSql verfügbar.
Wenn Sie diese Option verwenden, werden einige Validierungsschritte, z. B. die Validierung eindeutiger Einschränkungen, bis zum Commit der Transaktion verschoben. Wenn Sie last_statement verwenden, werden nachfolgende Vorgänge wie Lesevorgänge, Abfragen und DML in derselben Transaktion abgelehnt. Diese Option ist nicht mit Mutationen kompatibel. Wenn Sie Mutationen in dieselbe Transaktion einbeziehen, gibt Spanner einen Fehler zurück.
Die Option last_statement wird in den folgenden Clientbibliotheken unterstützt:
- Go in Version 1.77.0 oder höher
- Java in Version 2.27.0 oder höher
- Python in Version 3.53.0 oder höher
- PGAdapter in Version 0.45.0 oder höher
Sie wird standardmäßig unterstützt und aktiviert, wenn der Autocommit-Modus in den folgenden Treibern verwendet wird:
- JDBC-Treiber in Version 6.87.0 oder höher
- Go-Datenbank/SQL-Treiber in Version 1.11.2 oder höher
Python dbapi-Treiber in Version 3.53.0 oder höher
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)