Auf dieser Seite werden Transaktionen in Spanner beschrieben und die nicht schreibgeschützten, schreibgeschützten und partitionierten DML-Transaktionsschnittstellen von Spanner vorgestellt.
Bei einer Transaktion in Spanner handelt es sich um eine Gruppe von Lese- und Schreibvorgängen, die zu einem einzigen logischen Zeitpunkt über Spalten, Zeilen und Tabellen in einer Datenbank atomar ausgeführt wird.
In einer Sitzung werden Transaktionen in einer Spanner-Datenbank ausgeführt. Eine Sitzung stellt einen logischen Kommunikationskanal mit dem Spanner-Datenbankdienst dar. Sitzungen können eine oder mehrere Transaktionen gleichzeitig ausführen. Weitere Informationen finden Sie unter Sitzungen.
Transaktionstypen
Spanner unterstützt die folgenden Transaktionstypen, die jeweils für bestimmte Dateninteraktionsmuster entwickelt wurden:
Lesen/Schreiben:Diese Transaktionen verwenden eine pessimistische Sperrung und bei Bedarf einen zweiphasigen Commit. Sie können fehlschlagen und müssen dann wiederholt werden. Sie sind zwar auf eine einzelne Datenbank beschränkt, können aber Daten in mehreren Tabellen innerhalb dieser Datenbank ändern.
Schreibgeschützt:Diese Transaktionen garantieren Datenkonsistenz über mehrere Lesevorgänge hinweg, lassen jedoch keine Datenänderungen zu. Sie werden aus Konsistenzgründen mit einem vom System bestimmten Zeitstempel oder mit einem vom Nutzer konfigurierten Zeitstempel aus der Vergangenheit ausgeführt. Im Gegensatz zu Lese-Schreib-Transaktionen erfordern sie keinen Commit-Vorgang oder keine Sperren. Sie können jedoch pausieren, um auf den Abschluss laufender Schreibvorgänge zu warten.
Partitionierte DML:Bei diesem Transaktionstyp werden DML-Anweisungen als partitionierte DML-Vorgänge ausgeführt. Sie ist für umfangreiche Datenaktualisierungen und ‑löschungen optimiert, z. B. für die Bereinigung von Daten oder das Einfügen von Massendaten. Wenn Sie zahlreiche Schreibvorgänge ausführen müssen, für die keine atomare Transaktion erforderlich ist, sollten Sie Batch-Schreibvorgänge verwenden. Weitere Informationen finden Sie unter Daten mit Batch-Schreibvorgängen ändern.
Lese-/Schreibtransaktionen
Mit sperrenden Lese-/Schreibtransaktionen können Sie Daten an beliebiger Stelle in einer Datenbank atomar lesen, ändern und schreiben. Diese Art von Transaktion ist extern konsistent.
Minimieren Sie die Zeit, in der eine Transaktion aktiv ist. Kürzere Transaktionsdauern erhöhen die Wahrscheinlichkeit eines erfolgreichen Commits und verringern die Konflikte.
Spanner versucht, Lesesperren so lange aktiv zu halten, wie die Transaktion weiterhin Lesevorgänge ausführt und die Transaktion nicht durch sessions.commit
- oder sessions.rollback
-Vorgänge beendet wurde.
Wenn der Client über einen längeren Zeitraum inaktiv bleibt, kann es sein, dass Spanner die Sperren der Transaktion freigibt und die Transaktion abbricht.
Konzeptionell besteht eine Lese-Schreib-Transaktion aus null oder mehr Lese- oder SQL-Anweisungen, gefolgt von sessions.commit
. Der Client kann jederzeit vor sessions.commit
eine sessions.rollback
-Anfrage senden, um die Transaktion abzubrechen.
Wenn Sie einen Schreibvorgang ausführen möchten, der von einem oder mehreren Lesevorgängen abhängt, verwenden Sie eine sperrende Lese-Schreib-Transaktion:
- Wenn Sie einen oder mehrere Schreibvorgänge atomar ausführen müssen, führen Sie diese Schreibvorgänge in derselben Lese-Schreib-Transaktion aus. Wenn Sie beispielsweise 200 $ von Konto A auf Konto B überweisen, führen Sie beide Schreibvorgänge (Verringern von Konto A um 200 $und Erhöhen von Konto B um 200 $) und das Lesen der ursprünglichen Kontostände in derselben Transaktion aus.
- Wenn Sie den Kontostand von Konto A verdoppeln möchten, führen Sie die Lese- und Schreibvorgänge in derselben Transaktion aus. So wird sichergestellt, dass das System das Guthaben liest, bevor es verdoppelt und dann aktualisiert wird.
- Wenn Sie einen oder mehrere Schreibvorgänge ausführen könnten, die von den Ergebnissen eines oder mehrerer Lesevorgänge abhängen, sollten Sie diese Schreib- und Lesevorgänge in derselben Lese-Schreib-Transaktion ausführen, auch wenn die Schreibvorgänge nicht ausgeführt werden. Wenn Sie beispielsweise 200 $von Konto A auf Konto B überweisen möchten, aber nur, wenn der aktuelle Kontostand von A mehr als 500 $beträgt, sollten Sie das Lesen des Kontostands von A und die bedingten Schreibvorgänge in derselben Transaktion ausführen, auch wenn die Überweisung nicht erfolgt.
Verwenden Sie für Lesevorgänge eine einzelne Lesemethode oder eine schreibgeschützte Transaktion:
- Wenn Sie nur Lesevorgänge ausführen und den Lesevorgang mithilfe einer einzelnen Lesemethode ausdrücken können, verwenden Sie diese einzelne Lesemethode oder eine schreibgeschützte Transaktion. Im Gegensatz zu Lese-Schreib-Transaktionen werden bei einzelnen Lesevorgängen keine Sperren abgerufen.
Schnittstelle
Die Spanner-Clientbibliotheken bieten eine Schnittstelle zum Ausführen eines Arbeitsablaufs im Rahmen einer Lese-Schreib-Transaktion mit Wiederholungen für Transaktionsabbrüche. Für eine Spanner-Transaktion sind möglicherweise mehrere Wiederholungsversuche erforderlich, bevor sie mit Commit ausgeführt wird.
Transaktionen können aus verschiedenen Gründen abgebrochen werden. Wenn beispielsweise zwei Transaktionen versuchen, Daten gleichzeitig zu ändern, kann es zu einem Deadlock kommen. In solchen Fällen bricht Spanner eine Transaktion ab, damit die andere fortgesetzt werden kann. Weniger häufig können vorübergehende Ereignisse in Spanner auch zu Transaktionsabbrüchen führen.
Da Transaktionen atomar sind, hat eine abgebrochene Transaktion keine Auswirkungen auf die Datenbank. Versuchen Sie es noch einmal mit der Transaktion in derselben Sitzung, um die Erfolgsraten zu verbessern. Bei jedem Wiederholungsversuch, der zu einem ABORTED
-Fehler führt, erhöht sich die Sperrpriorität der Transaktion.
Wenn Sie eine Transaktion in einer Spanner-Clientbibliothek verwenden, definieren Sie den Ablauf der Transaktion als Funktionsobjekt. Diese Funktion kapselt die Lese- und Schreibvorgänge, die für eine oder mehrere Datenbanktabellen ausgeführt werden. Die Spanner-Clientbibliothek führt diese Funktion wiederholt aus, bis die Transaktion entweder erfolgreich übergeben wird oder ein Fehler auftritt, der nicht wiederholt werden kann.
Beispiel
Angenommen, Sie haben in der Tabelle Albums
die Spalte MarketingBudget
:
CREATE TABLE Albums ( SingerId INT64 NOT NULL, AlbumId INT64 NOT NULL, AlbumTitle STRING(MAX), MarketingBudget INT64 ) PRIMARY KEY (SingerId, AlbumId);
Ihre Marketingabteilung bittet Sie, 200.000 $aus dem Budget von Albums
(2, 2)
in das Budget von Albums (1, 1)
zu verschieben, aber nur, wenn das Geld im Budget dieses Albums verfügbar ist. Sie sollten für diesen Vorgang eine sperrende Lese-Schreib-Transaktion verwenden, da die Transaktion je nach Ergebnis eines Lesevorgangs Schreibvorgänge ausführen könnte.
Das folgende Beispiel zeigt, wie Sie eine Lese-Schreib-Transaktion ausführen:
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
Semantik
In diesem Abschnitt wird die Semantik für Lese-/Schreibtransaktionen in Spanner beschrieben.
Eigenschaften
Eine Lese-Schreib-Transaktion in Spanner führt eine Reihe von Lese- und Schreibvorgängen atomar aus. Der Zeitstempel, zu dem Lese-/Schreibtransaktionen ausgeführt werden, entspricht der verstrichenen Zeit. Die Reihenfolge der Serialisierung entspricht dieser Zeitstempelreihenfolge.
Lese-/Schreibtransaktionen bieten die ACID-Attribute von relationalen Datenbanken. Lese-Schreib-Transaktionen in Spanner bieten stärkere Attribute als typische ACID-Transaktionen.
Aufgrund dieser Attribute können Sie sich als Anwendungsentwickler auf die Genauigkeit jeder einzelnen Transaktion konzentrieren, ohne sich um den Schutz der ausgeführten Transaktion vor anderen Transaktionen, die möglicherweise zur gleichen Zeit ausgeführt werden könnten, kümmern zu müssen.
Isolation für Lese-/Schreibtransaktionen
Nachdem Sie eine Transaktion mit einer Reihe von Lese- und Schreibvorgängen erfolgreich committet haben, sehen Sie Folgendes:
- Die Transaktion gibt Werte zurück, die einen konsistenten Snapshot zum Commit-Zeitstempel der Transaktion widerspiegeln.
- Leere Zeilen oder Bereiche bleiben beim Commit leer.
- Bei der Transaktion werden alle Schreibvorgänge zum Commit-Zeitstempel der Transaktion committet.
- Keine Transaktion kann die Schreibvorgänge sehen, bis die Transaktion übergeben wurde.
Spanner-Clienttreiber enthalten eine Logik für das Wiederholen von Transaktionen, die vorübergehende Fehler maskiert, indem die Transaktion noch einmal ausgeführt und die vom Client beobachteten Daten validiert werden.
Der Effekt besteht darin, dass alle Lese- und Schreibvorgänge zu einem bestimmten Zeitpunkt stattgefunden haben, sowohl aus der Sicht der Transaktion selbst als auch aus der Sicht anderer Leser und Autoren in der Spanner-Datenbank. Das bedeutet, dass die Lese- und Schreibvorgänge zum selben Zeitpunkt erfolgen. Ein Beispiel finden Sie unter Serialisierbarkeit und externe Konsistenz.
Isolation für Lesetransaktionen
Wenn eine Lese-Schreib-Transaktion nur Lesevorgänge ausführt, bietet sie ähnliche Konsistenzgarantien wie eine schreibgeschützte Transaktion. Alle Lesevorgänge innerhalb der Transaktion geben Daten aus einem konsistenten Zeitstempel zurück, einschließlich der Bestätigung nicht vorhandener Zeilen.
Ein Unterschied besteht darin, wenn eine Lese-/Schreibtransaktion ohne Ausführung eines Schreibvorgangs committet wird. In diesem Szenario gibt es keine Garantie dafür, dass die innerhalb der Transaktion gelesenen Daten in der Datenbank zwischen dem Lesevorgang und dem Commit der Transaktion unverändert geblieben sind.
Um die Aktualität der Daten zu gewährleisten und zu prüfen, ob die Daten seit dem letzten Abruf geändert wurden, ist ein nachfolgender Lesevorgang erforderlich. Dieser erneute Lesevorgang kann entweder innerhalb einer anderen Lese-Schreib-Transaktion oder mit einem starken Lesevorgang erfolgen.
Wenn eine Transaktion ausschließlich Lesevorgänge ausführt, sollten Sie für optimale Effizienz eine schreibgeschützte Transaktion anstelle einer Lese-Schreib-Transaktion verwenden.
Atomarität, Konsistenz, Langlebigkeit
Zusätzlich zur Isolation bietet Spanner die anderen ACID-Eigenschaften:
- Atomarität: Eine Transaktion gilt als atomar, wenn alle ihre Vorgänge erfolgreich abgeschlossen werden oder keiner von ihnen. Wenn ein Vorgang innerhalb einer Transaktion fehlschlägt, wird die gesamte Transaktion auf ihren ursprünglichen Zustand zurückgesetzt, um die Datenintegrität zu gewährleisten.
- Konsistenz. Eine Transaktion muss die Integrität der Regeln und Einschränkungen der Datenbank aufrechterhalten. Nach Abschluss einer Transaktion sollte sich die Datenbank in einem gültigen Zustand befinden, der den vordefinierten Regeln entspricht.
- Langlebigkeit. Nachdem eine Transaktion übernommen wurde, werden ihre Änderungen dauerhaft in der Datenbank gespeichert und bleiben auch bei Systemausfällen, Stromausfällen oder anderen Störungen erhalten.
Serialisierbarkeit und externe Konsistenz
Spanner bietet starke Transaktionsgarantien, einschließlich Serialisierbarkeit und externer Konsistenz. Diese Eigenschaften sorgen dafür, dass die Daten konsistent bleiben und Vorgänge in einer vorhersehbaren Reihenfolge ausgeführt werden, auch in einer verteilten Umgebung.
Die Serialisierbarkeit sorgt dafür, dass alle Transaktionen so erscheinen, als würden sie nacheinander in einer einzigen sequenziellen Reihenfolge ausgeführt, auch wenn sie gleichzeitig verarbeitet werden. Spanner erreicht dies, indem Commit-Zeitstempel für Transaktionen zugewiesen werden, die die Reihenfolge widerspiegeln, in der sie übernommen wurden.
Spanner bietet eine noch stärkere Garantie, die als externe Konsistenz bezeichnet wird. Das bedeutet, dass Transaktionen nicht nur in einer Reihenfolge übernommen werden, die von ihren Commit-Zeitstempeln widergespiegelt wird, sondern dass diese Zeitstempel auch mit der realen Zeit übereinstimmen. So können Sie Commit-Zeitstempel mit der Echtzeit vergleichen und erhalten eine einheitliche und global geordnete Ansicht Ihrer Daten.
Wenn eine Transaktion Txn1
vor einer anderen Transaktion Txn2
in Echtzeit festgeschrieben wird, ist der Commit-Zeitstempel von Txn1
früher als der Commit-Zeitstempel von Txn2
.
Dazu ein Beispiel:
In diesem Szenario gilt für den Zeitraum t
:
- Bei Transaktion
Txn1
werden DatenA
gelesen, ein Schreibvorgang fürA
wird vorbereitet und dann erfolgreich übergeben. - Transaktion
Txn2
beginnt nach dem Start vonTxn1
. Zuerst werden DatenB
und dann DatenA
gelesen.
Obwohl Txn2
vor Abschluss von Txn1 gestartet wurde, werden die von Txn1
an A
vorgenommenen Änderungen in Txn2
berücksichtigt. Das liegt daran, dass Txn2
A
liest, nachdem Txn1
seinen Schreibvorgang für A
committet hat.
Die Ausführungszeiten von Txn1
und Txn2
können sich zwar überschneiden, aber die Commit-Zeitstempel c1
und c2
erzwingen eine lineare Transaktionsreihenfolge. Das bedeutet:
- Alle Lese- und Schreibvorgänge in
Txn1
scheinen zu einem einzigen Zeitpunkt,c1
, erfolgt zu sein. - Alle Lese- und Schreibvorgänge in
Txn2
scheinen zu einem einzigen Zeitpunkt,c2
, erfolgt zu sein. - Wichtig ist, dass
c1
für übergebene Schreibvorgänge vorc2
liegt, auch wenn die Schreibvorgänge auf verschiedenen Geräten ausgeführt wurden. WennTxn2
nur Lesevorgänge ausführt, istc1
früher oder gleichzeitig mitc2
.
Diese starke Reihenfolge bedeutet, dass ein nachfolgender Lesevorgang, der die Auswirkungen von Txn2
beobachtet, auch die Auswirkungen von Txn1
beobachtet. Dieses Attribut ist für alle erfolgreich ausgeführten Transaktionen „true“.
Lese- und Schreibgarantien bei Transaktionsfehlern
Wenn ein Aufruf zum Ausführen einer Transaktion fehlschlägt, hängen Ihre Lese- und Schreibgarantien davon ab, welcher Fehler beim zugrunde liegenden Commit-Aufruf für das Fehlschlagen verantwortlich war.
Zum Beispiel bedeutet das Auftreten eines Fehlers der Art „Zeile nicht gefunden“ oder „Zeile existiert bereits“, dass beim Schreiben der gepufferten Mutationen ein Fehler aufgetreten ist, z. B. dass eine Zeile, die der Client zu aktualisieren versucht, nicht vorhanden ist. In diesem Fall sind die Lesevorgänge garantiert konsistent, die Schreibvorgänge werden nicht angewendet und das Nicht-Vorhandensein einer Zeile ist ebenfalls mit den Lesevorgängen garantiert konsistent.
Lese- und Schreibgarantien bei Transaktionsfehlern
Wenn eine Spanner-Transaktion fehlschlägt, hängen die Garantien, die Sie für Lese- und Schreibvorgänge erhalten, vom jeweiligen Fehler ab, der während des commit
-Vorgangs aufgetreten ist.
Eine Fehlermeldung wie „Zeile nicht gefunden“ oder „Zeile existiert bereits“ weist beispielsweise auf ein Problem beim Schreiben gepufferter Mutationen hin. Das kann beispielsweise passieren, wenn eine Zeile, die der Client zu aktualisieren versucht, nicht vorhanden ist. In diesen Szenarien:
- Lesevorgänge sind konsistent:Alle während der Transaktion gelesenen Daten sind garantiert bis zum Zeitpunkt des Fehlers konsistent.
- Schreibvorgänge werden nicht angewendet:Die Mutationen, die in der Transaktion versucht wurden, werden nicht in der Datenbank gespeichert.
- Zeilenkonsistenz:Die Nichtexistenz (oder der vorhandene Status) der Zeile, die den Fehler ausgelöst hat, stimmt mit den Lesevorgängen überein, die innerhalb der Transaktion ausgeführt wurden.
Sie können asynchrone Leseoperationen in Spanner jederzeit abbrechen, ohne andere laufende Operationen innerhalb derselben Transaktion zu beeinträchtigen. Diese Flexibilität ist nützlich, wenn eine Operation auf höherer Ebene abgebrochen wird oder Sie entscheiden, einen Lesevorgang basierend auf den ersten Ergebnissen abzubrechen.
Wenn Sie jedoch das Abbrechen eines Lesevorgangs anfordern, wird dieser nicht unbedingt sofort beendet. Nach einer Stornierungsanfrage kann der Lesevorgang weiterhin:
- Erfolgreich abgeschlossen:Die Verarbeitung des Lesevorgangs wird möglicherweise abgeschlossen und es werden Ergebnisse zurückgegeben, bevor die Abbrechen-Anfrage wirksam wird.
- Fehler aus einem anderen Grund:Der Lesevorgang konnte aufgrund eines anderen Fehlers beendet werden, z. B. durch einen Abbruch.
- Unvollständige Ergebnisse zurückgeben:Der Lesevorgang kann Teilergebnisse zurückgeben, die dann im Rahmen des Transaktions-Commits validiert werden.
Es ist auch wichtig, den Unterschied zu commit
-Vorgängen zu beachten: Wenn Sie einen commit
-Vorgang abbrechen, wird die gesamte Transaktion abgebrochen, es sei denn, die Transaktion wurde bereits übergeben oder ist aus einem anderen Grund fehlgeschlagen.
Leistung
In diesem Abschnitt werden Probleme beschrieben, die sich auf die Leistung von Lese-/Schreibtransaktionen auswirken.
Sperren der Nebenläufigkeitserkennung
Mit Spanner können mehrere Clients gleichzeitig mit derselben Datenbank interagieren. Um die Datenkonsistenz bei diesen gleichzeitigen Transaktionen aufrechtzuerhalten, verwendet Spanner einen Sperrmechanismus, der sowohl gemeinsame als auch exklusive Sperren nutzt.
Wenn eine Transaktion einen Lesevorgang ausführt, ruft Spanner gemeinsam genutzte Lesesperren für die relevanten Daten ab. Mit diesen freigegebenen Sperren können andere gleichzeitige Lesevorgänge auf dieselben Daten zugreifen. Diese Parallelität wird beibehalten, bis die Transaktion ihre Änderungen committet.
Während der Commit-Phase, wenn Schreibvorgänge angewendet werden, versucht die Transaktion, ihre Sperren auf exklusive Sperren zu aktualisieren. Dazu wird Folgendes getan:
- Blockiert alle neuen Anfragen für gemeinsam genutzte Lesesperren für die betroffenen Daten.
- Wartet, bis alle vorhandenen gemeinsam genutzten Lesesperren für diese Daten freigegeben werden.
- Nachdem alle gemeinsam genutzten Lesesperren aufgehoben wurden, wird eine exklusive Sperre gesetzt, die für die Dauer des Schreibvorgangs den alleinigen Zugriff auf die Daten ermöglicht.
Hinweise zu Sperren:
- Granularität:In Spanner werden Sperren auf Zeilen- und Spaltenebene angewendet. Wenn die Transaktion
T1
eine Sperre für die SpalteA
der Zeilealbumid
enthält, kann die TransaktionT2
weiterhin gleichzeitig in die SpalteB
derselben Zeilealbumid
schreiben, ohne dass es zu Konflikten kommt. - Schreibvorgänge ohne Lesevorgänge:Für Schreibvorgänge ohne Lesevorgänge ist in Spanner keine exklusive Sperre erforderlich. Stattdessen wird eine gemeinsam genutzte Sperre verwendet. Das liegt daran, dass die Reihenfolge der Anwendung für Schreibvorgänge ohne Lesevorgänge durch ihre Commit-Zeitstempel bestimmt wird. So können mehrere Writer gleichzeitig ohne Konflikt auf dasselbe Element zugreifen. Eine exklusive Sperre ist nur erforderlich, wenn Ihre Transaktion zuerst die Daten liest, die sie schreiben möchte.
- Sekundäre Indexe für Zeilensuchen:Wenn Sie Zeilensuchen innerhalb einer Lese-/Schreibtransaktion ausführen, kann die Verwendung sekundärer Indexe die Leistung erheblich verbessern. Wenn Sie sekundäre Indexe verwenden, um die gescannten Zeilen auf einen kleineren Bereich zu beschränken, sperrt Spanner weniger Zeilen in der Tabelle. Dadurch können mehr Zeilen außerhalb dieses bestimmten Bereichs gleichzeitig geändert werden.
- Exklusiver Zugriff auf externe Ressourcen:Die internen Sperren von Spanner sind für die Datenkonsistenz innerhalb der Spanner-Datenbank selbst konzipiert. Verwenden Sie sie nicht, um den exklusiven Zugriff auf Ressourcen außerhalb von Spanner zu garantieren. Spanner kann Transaktionen aus verschiedenen Gründen abbrechen, z. B. aufgrund interner Systemoptimierungen wie dem Verschieben von Daten zwischen Rechenressourcen. Wenn eine Transaktion wiederholt wird (entweder explizit durch Ihren Anwendungscode oder implizit durch Clientbibliotheken wie den Spanner-JDBC-Treiber), wird nur garantiert, dass die Sperren während des erfolgreichen Commit-Versuchs bestanden haben.
- Sperrstatistiken:Mit dem Introspektionstool Sperrstatistiken können Sie Sperrkonflikte in Ihrer Datenbank diagnostizieren und untersuchen.
Deadlock-Erkennung
Spanner erkennt, wenn mehrere Transaktionen blockiert werden, und zwingt alle bis auf eine der Transaktionen zum Abbrechen. Betrachten Sie das folgende Szenario: Txn1
enthält eine Sperre für Datensatz A
und wartet auf eine Sperre für Datensatz B
, während Txn2
eine Sperre für Datensatz B
enthält und auf eine Sperre für Datensatz A
wartet. Um dieses Problem zu beheben, muss eine der Transaktionen abgebrochen werden, damit die Sperre freigegeben wird und die andere Transaktion fortgesetzt werden kann.
Spanner verwendet den Standardalgorithmus „wound-wait“ für die Deadlock-Erkennung. Spanner verfolgt das Alter jeder Transaktion, die in Konflikt stehende Sperren anfordert. Es ermöglicht älteren Transaktionen, jüngere Transaktionen abzubrechen. Eine ältere Transaktion ist eine Transaktion, deren frühester Lese-, Abfrage- oder Commit-Vorgang früher stattgefunden hat.
Durch die Priorisierung älterer Transaktionen sorgt Spanner dafür, dass jede Transaktion schließlich Sperren erhält, nachdem sie alt genug ist, um eine höhere Priorität zu haben. So kann beispielsweise eine ältere Transaktion, die eine vom Autor freigegebene Sperre benötigt, eine jüngere Transaktion abbrechen, die eine vom Leser freigegebene Sperre enthält.
Verteilte Ausführung
Spanner kann Transaktionen für Daten ausführen, die sich über mehrere Server erstrecken. Diese Funktion ist jedoch mit Leistungskosten verbunden, die mit denen von Transaktionen auf nur einem Server zu vergleichen sind.
Welche Arten von Transaktionen können verteilt sein? Spanner kann die Verantwortung für Datenbankzeilen auf viele Server verteilen. Normalerweise werden eine Zeile und die entsprechenden Zeilen in verschränkten Tabellen vom selben Server verarbeitet, ebenso wie zwei Zeilen in derselben Tabelle mit ähnlichen Schlüsseln. Spanner kann Transaktionen mit Zeilen über mehrere Server ausführen. Allerdings gilt als Faustregel, dass Transaktionen, die viele Zeilen an einem Standort betreffen, schneller und günstiger sind als Transaktionen, die viele in der Datenbank oder in einer großen Tabelle verteilte Zeilen betreffen.
Zu den effizientesten Transaktionen in Spanner gehören nur Lese- und Schreibvorgänge, die atomar angewendet werden sollen. Transaktionen sind am schnellsten, wenn alle Lese- und Schreibzugriffe auf Daten im selben Teil des Schlüsselbereichs erfolgen.
Schreibgeschützte Transaktionen
Zusätzlich zu sperrenden Lese-Schreib-Transaktionen bietet Spanner schreibgeschützte Transaktionen.
Verwenden Sie eine schreibgeschützte Transaktion, wenn Sie mehr als einen Lesevorgang mit demselben Zeitstempel ausführen müssen. Wenn Sie Ihren Lesevorgang mithilfe einer einzelnen Lesemethode von Spanner ausdrücken können, sollten Sie stattdessen diese einzelne Lesemethode verwenden. Die Leistung bei der Verwendung eines solchen einzelnen Leseaufrufs sollte mit der Leistung eines einzelnen Lesevorgangs vergleichbar sein, der in einer schreibgeschützten Transaktion ausgeführt wird.
Wenn Sie eine große Datenmenge lesen, sollten Sie Partitionen verwenden, um die Daten parallel zu lesen.
Da schreibgeschützte Transaktionen keine Schreibvorgänge ausführen, haben sie keine Sperren und blockieren andere Transaktionen nicht. Schreibgeschützte Transaktionen erkennen ein konsistentes Präfix des Commit-Verlaufs der Transaktion, damit Ihre Anwendung immer konsistente Daten erhält.
Schnittstelle
Spanner bietet eine Schnittstelle zum Ausführen eines Arbeitsablaufs im Rahmen einer schreibgeschützten Transaktion mit Wiederholungen für Transaktionsabbrüche.
Beispiel
Im folgenden Beispiel wird gezeigt, wie eine schreibgeschützte Transaktion verwendet werden kann, um konsistente Daten für zwei Lesevorgänge zum selben Zeitstempel zu erhalten:
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
Semantik
In diesem Abschnitt wird die Semantik für schreibgeschützte Transaktionen beschrieben.
Schreibgeschützte Snapshot-Transaktionen
Wenn eine schreibgeschützte Transaktion in Spanner ausgeführt wird, werden alle Lesevorgänge zu einem einzigen logischen Zeitpunkt ausgeführt. Das bedeutet, dass sowohl die schreibgeschützte Transaktion als auch alle anderen gleichzeitigen Leser und Autoren zu diesem bestimmten Zeitpunkt einen konsistenten Snapshot der Datenbank sehen.
Diese schreibgeschützten Snapshot-Transaktionen bieten im Vergleich zu sperrenden Lese-Schreib-Transaktionen einen einfacheren Ansatz für konsistente Lesevorgänge. Das kann folgende Gründe haben:
- Keine Sperren:Schreibgeschützte Transaktionen erhalten keine Sperren. Stattdessen wird ein Spanner-Zeitstempel ausgewählt und alle Lesevorgänge werden für diese historische Version der Daten ausgeführt. Da sie keine Sperren verwenden, blockieren sie keine gleichzeitigen Lese- und Schreibtransaktionen.
- Keine Abbrüche:Diese Transaktionen werden nie abgebrochen. Sie können fehlschlagen, wenn der ausgewählte Lesetime-Stempel bereinigt wird. Die standardmäßige Richtlinie für die automatische Speicherbereinigung von Spanner ist in der Regel jedoch großzügig genug, sodass die meisten Anwendungen dieses Problem nicht haben.
- Keine Commits oder Rollbacks:Für schreibgeschützte Transaktionen sind keine Aufrufe von
sessions.commit
odersessions.rollback
erforderlich und sie werden sogar verhindert.
Zum Ausführen einer Snapshot-Transaktion definiert der Client eine Zeitstempelgrenze, die Spanner anweist, wie ein Lesezeitstempel ausgewählt werden soll. Es gibt folgende Arten von Zeitstempelgrenzen:
- Starke Lesevorgänge:Bei diesen Lesevorgängen wird garantiert, dass Sie die Auswirkungen aller Transaktionen sehen, die vor Beginn des Lesevorgangs durchgeführt wurden. Alle Zeilen in einem einzelnen Lesevorgang sind konsistent. Starke Lesevorgänge sind jedoch nicht wiederholbar. Sie geben zwar einen Zeitstempel zurück, aber ein erneuter Lesevorgang mit demselben Zeitstempel ist wiederholbar. Zwei aufeinanderfolgende starke schreibgeschützte Transaktionen können aufgrund gleichzeitiger Schreibvorgänge unterschiedliche Ergebnisse liefern. Für Abfragen für Änderungsstreams muss diese Grenze verwendet werden. Weitere Informationen finden Sie unter TransactionOptions.ReadOnly.strong.
- Exakte Veralterung:Bei dieser Option werden Lesevorgänge zu einem von Ihnen angegebenen Zeitstempel ausgeführt, entweder als absoluter Zeitstempel oder als Veralterungsdauer relativ zur aktuellen Zeit. So wird sichergestellt, dass Sie bis zu diesem Zeitstempel ein konsistentes Präfix des globalen Transaktionsverlaufs sehen, und es werden in Konflikt stehende Transaktionen blockiert, die mit einem Zeitstempel kleiner oder gleich dem Lesezeitstempel committen könnten. Dieser Modus ist etwas schneller als die Modi mit begrenzter Veraltung, gibt aber möglicherweise ältere Daten zurück. Weitere Informationen finden Sie unter TransactionOptions.ReadOnly.read_timestamp und TransactionOptions.ReadOnly.exact_staleness.
- Begrenzte Veralterung:Spanner wählt den neuesten Zeitstempel innerhalb eines nutzerdefinierten Veralterungslimits aus, sodass die Ausführung am nächstgelegenen verfügbaren Replikat ohne Blockierung möglich ist. Alle zurückgegebenen Zeilen sind konsistent. Wie bei starken Lesevorgängen ist die begrenzte Veraltung nicht wiederholbar, da verschiedene Lesevorgänge auch bei derselben Grenze zu unterschiedlichen Zeitstempeln ausgeführt werden können. Diese Lesevorgänge werden in zwei Phasen ausgeführt (Zeitstempel-Aushandlung, dann Lesen) und sind in der Regel etwas langsamer als exakt veraltete Lesevorgänge. Sie geben jedoch oft neuere Ergebnisse zurück und werden mit größerer Wahrscheinlichkeit auf einem lokalen Replikat ausgeführt. Dieser Modus ist nur für schreibgeschützte Einmaltransaktionen verfügbar, da für die Zeitstempelvereinbarung im Voraus bekannt sein muss, welche Zeilen gelesen werden. Weitere Informationen finden Sie unter TransactionOptions.ReadOnly.max_staleness und TransactionOptions.ReadOnly.min_read_timestamp.
Partitionierte DML-Transaktionen
Mit partitionierten DML-Anweisungen können Sie umfangreiche Anweisungen des Typs UPDATE
und DELETE
ausführen, ohne die Transaktionslimits zu überschreiten oder eine ganze Tabelle zu sperren. Spanner erreicht dies, indem der Schlüsselbereich partitioniert und die DML-Anweisungen in jeder Partition in einer separaten Lese-/Schreibtransaktion ausgeführt werden.
Wenn Sie nicht partitionierte DML verwenden möchten, führen Sie Anweisungen in Lese-/Schreibtransaktionen aus, die Sie explizit in Ihrem Code erstellen. Weitere Informationen finden Sie unter DML verwenden.
Schnittstelle
Spanner bietet die Schnittstelle TransactionOptions.partitionedDml zum Ausführen einer einzelnen partitionierten DML-Anweisung.
Beispiele
Mit den folgenden Codebeispielen wird die Spalte MarketingBudget
der Tabelle Albums
aktualisiert.
C++
Zum Ausführen einer partitionierten DML-Anweisung verwenden Sie die Funktion ExecutePartitionedDml()
.
C#
Zum Ausführen einer partitionierten DML-Anweisung verwenden Sie die Methode ExecutePartitionedUpdateAsync()
.
Go
Zum Ausführen einer partitionierten DML-Anweisung verwenden Sie die Methode PartitionedUpdate()
.
Java
Zum Ausführen einer partitionierten DML-Anweisung verwenden Sie die Methode executePartitionedUpdate()
.
Node.js
Zum Ausführen einer partitionierten DML-Anweisung verwenden Sie die Methode runPartitionedUpdate()
.
PHP
Zum Ausführen einer partitionierten DML-Anweisung verwenden Sie die Methode executePartitionedUpdate()
.
Python
Zum Ausführen einer partitionierten DML-Anweisung verwenden Sie die Methode execute_partitioned_dml()
.
Ruby
Zum Ausführen einer partitionierten DML-Anweisung verwenden Sie die Methode execute_partitioned_update()
.
Im folgenden Codebeispiel werden Zeilen aus der Tabelle Singers
anhand der Spalte SingerId
gelöscht.
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
Semantik
In diesem Abschnitt wird die Semantik für partitionierte DML beschrieben.
Ausführung partitionierter DML-Anweisungen
Sie können jeweils nur eine partitionierte DML-Anweisung ausführen, unabhängig davon, ob Sie eine Clientbibliotheksmethode oder die Google Cloud CLI verwenden.
Partitionierte Transaktionen unterstützen keine Commits oder Rollbacks. Spanner führt die DML-Anweisung sofort aus und wendet sie an. Wenn Sie den Vorgang abbrechen oder der Vorgang fehlschlägt, bricht Spanner alle ausgeführten Partitionen ab und startet keine verbleibenden Partitionen. Cloud Spanner führt für bereits ausgeführte Partitionen jedoch kein Rollback aus.
Strategie zum Abrufen von Sperren für partitionierte DML
Um Konflikte aufgrund von Sperren zu reduzieren, werden bei partitionierter DML nur Lesesperren für Zeilen übernommen, die der WHERE
-Klausel entsprechen. Kleinere, unabhängige Transaktionen, die für jede Partition verwendet werden, halten Sperren auch kürzer.
Transaktionslimits für Sitzungen
In jeder Sitzung in Spanner kann jeweils nur eine aktive Transaktion vorhanden sein. Dazu gehören eigenständige Lesevorgänge und Abfragen, die intern eine Transaktion verwenden und auf dieses Limit angerechnet werden. Nach Abschluss einer Transaktion kann die Sitzung sofort für die nächste Transaktion wiederverwendet werden. Es ist nicht erforderlich, für jede Transaktion eine neue Sitzung zu erstellen.
Alte Zeitstempel für Lesezugriffe und automatische Speicherbereinigung von Versionen
In Spanner wird die automatische Speicherbereinigung für Versionen durchgeführt, um gelöschte oder überschriebene Daten zu erfassen und Speicherplatz zurückzugewinnen. Standardmäßig werden Daten, die älter als eine Stunde sind, zurückgefordert. Spanner kann keine Lesevorgänge mit Zeitstempeln ausführen, die älter als das konfigurierte VERSION_RETENTION_PERIOD
sind. Der Standardwert ist eine Stunde, kann aber auf bis zu eine Woche konfiguriert werden. Wenn Lesevorgänge während der Ausführung zu alt werden, schlagen sie fehl und geben den Fehler FAILED_PRECONDITION
zurück.
Abfragen für Änderungsstreams
Ein Änderungsstream ist ein Schemaobjekt, das Sie so konfigurieren können, dass Datenänderungen in einer gesamten Datenbank, in bestimmten Tabellen oder in einer definierten Gruppe von Spalten in einer Datenbank überwacht werden.
Wenn Sie einen Änderungsstream erstellen, definiert Spanner eine entsprechende SQL-Tabellenwertfunktion (Table-Value-Funktion, TVF). Mit dieser TVF können Sie die Änderungsdatensätze im zugehörigen Änderungsstream mit der Methode sessions.executeStreamingSql
abfragen. Der Name der TVF wird aus dem Namen des Änderungsstreams generiert und beginnt immer mit READ_
.
Alle Abfragen für Change Stream-TVFs müssen mit der sessions.executeStreamingSql
-API in einer einmaligen schreibgeschützten Transaktion mit einem starken schreibgeschützten timestamp_bound
ausgeführt werden. Mit der TVF für Änderungsstreams können Sie start_timestamp
und end_timestamp
für den Zeitraum angeben. Alle Änderungsdatensätze innerhalb des Aufbewahrungszeitraums sind über diese starke schreibgeschützte timestamp_bound
zugänglich. Alle anderen TransactionOptions
sind für Change Stream-Abfragen ungültig.
Wenn TransactionOptions.read_only.return_read_timestamp
auf true
gesetzt ist, gibt die Transaction
-Nachricht, die die Transaktion beschreibt, den Sonderwert 2^63 - 2
anstelle eines gültigen Lesezeitstempels zurück. Sie sollten diesen Sonderwert verwerfen und nicht für nachfolgende Anfragen verwenden.
Weitere Informationen finden Sie unter Workflow für Change Streams-Abfragen.
Inaktive Transaktionen
Eine Transaktion gilt als inaktiv, wenn keine ausstehenden Lese- oder SQL-Abfragen vorhanden sind und in den letzten 10 Sekunden keine gestartet wurde. Spanner kann inaktive Transaktionen abbrechen, um zu verhindern, dass sie Sperren unbegrenzt lange halten. Wenn eine inaktive Transaktion abgebrochen wird, schlägt der Commit fehl und es wird ein ABORTED
-Fehler zurückgegeben.
Wenn Sie in der Transaktion regelmäßig eine kleine Abfrage wie SELECT 1
ausführen, kann verhindert werden, dass sie inaktiv wird.