Auf dieser Seite werden Transaktionen in Spanner beschrieben und die Transaktionsschnittstellen für Lese-/Schreib-, schreibgeschützte und partitionierte DML-Transaktionen in Spanner vorgestellt.
Eine Transaktion in Spanner ist eine Gruppe von Lese- und Schreibvorgängen. Alle Vorgänge in einer Transaktion sind atomar. Das bedeutet, dass sie entweder alle erfolgreich sind oder alle fehlschlagen.
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 der standardmäßigen serialisierbaren Isolationsebene können Lese-/Schreibtransaktionen Daten in einer Datenbank atomar lesen, ändern und schreiben. Diese Art von Transaktion ist extern konsistent.
Wenn Sie Lese-/Schreibtransaktionen verwenden, empfehlen wir, die Zeit zu minimieren, 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 Spanner die Sperren der Transaktion freigeben und die Transaktion abbrechen.
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 die standardmäßige serialisierbare Isolationsebene und sperren Sie die 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 es verdoppelt und dann aktualisiert.
- 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 bei der serialisierbaren Isolation 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. In selteneren Fällen können vorübergehende Ereignisse in Spanner auch zu Transaktionsabbrüchen führen.
Alle Lese-/Schreibtransaktionen bieten die ACID-Attribute von relationalen Datenbanken. 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. Außerdem enthalten Spanner-Clienttreiber eine interne Logik für Transaktionswiederholungen, die vorübergehende Fehler maskiert, indem die Transaktion noch einmal ausgeführt wird.
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 committet 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-/Schreibtransaktion mit der standardmäßigen serialisierbaren Isolationsebene von Spanner 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
Die serialisierbare Isolation ist die Standardisolationsebene in Spanner. Bei serialisierbarer Isolation bietet Spanner Kunden die besten Garantien der Gleichzeitigkeitserkennung für Transaktionen. Dies wird als externe Konsistenz bezeichnet. Bei einer Lese-Schreib-Transaktion wird eine Reihe von Lese- und Schreibvorgängen atomar ausgeführt. 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. Daher lässt Spanner standardmäßig zu, dass Schreibvorgänge fortfahren, ohne von schreibgeschützten Transaktionen blockiert zu werden. Dabei zeigen sie allerdings nicht die Anomalien auf, die bei der Snapshot-Isolation erlaubt sind. Aufgrund dieser Attribute können Sie sich als Anwendungsentwickler auf die Richtigkeit 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.
Sie können Ihre Lese-Schreib-Transaktionen auch mit der Isolation „Repeatable Read“ ausführen. Die Isolationsebene „Repeatable Read“ in Spanner sorgt dafür, dass bei allen Lesevorgängen innerhalb einer Transaktion ein konsistenter oder starker Snapshot der Datenbank angezeigt wird, wie er zu Beginn der Transaktion vorhanden war. Weitere Informationen finden Sie unter Isolationsebene „Wiederholbares Lesen“.
Lese-/Schreibtransaktionen mit serialisierbarer Isolation
Nachdem eine Transaktion, die eine Reihe von Lese- und Schreibvorgängen in der standardmäßigen serialisierbaren Isolation enthält, erfolgreich committet wurde, gilt 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 mit Commit gespeichert.
- 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.
Lese-/Schreibtransaktionen mit der Isolationsebene „Repeatable Read“
Nachdem eine Transaktion mit der Isolationsstufe „Wiederholbarer Lesevorgang“ erfolgreich mit Commit ausgeführt wurde, gilt Folgendes:
- Die Transaktion gibt Werte zurück, die einen konsistenten Snapshot der Datenbank widerspiegeln. Der Snapshot wird in der Regel während des ersten Transaktionsvorgangs erstellt, der möglicherweise nicht mit dem Commit-Zeitstempel übereinstimmt.
- Da „Repeatable Read“ mit Snapshot-Isolation implementiert wird, werden alle Schreibvorgänge der Transaktion nur dann zum Commit-Zeitstempel der Transaktion durchgeführt, wenn sich die Schreibmenge zwischen dem Zeitstempel des Transaktions-Snapshots und dem Commit-Zeitstempel nicht geändert hat.
- Andere Transaktionen sehen die Schreibvorgänge erst, nachdem die früheste Transaktion übergeben wurde.
Isolation für Lese-/Schreibtransaktionen mit Nur-Lese-Vorgängen
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.
- Beständigkeit: Nachdem eine Transaktion committet 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
Standardmäßig bietet Spanner 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 während des Zeitraums 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 in Txn2
die Änderungen berücksichtigt, die von Txn1
an A
vorgenommen wurden. Das liegt daran, dass Txn2
A
liest, nachdem Txn1
seinen Schreibvorgang für A
abgeschlossen 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 Computern 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“.
Wenn Sie dagegen die Isolation „Repeatable Read“ verwenden, tritt für dieselben Transaktionen das folgende Szenario ein:
Txn1
beginnt mit dem Lesen von DatenA
und erstellt einen eigenen Snapshot der Datenbank zu diesem Zeitpunkt.Txn2
beginnt, liest DatenB
und erstellt einen eigenen Snapshot.- Als Nächstes ändert
Txn1
die DatenA
und führt die Änderungen erfolgreich aus. Txn2
Versuche, Daten zu lesenA
. DaTxn2
auf einem früheren Snapshot basiert, wird die AktualisierungTxn1
, die gerade anA
vorgenommen wurde, nicht berücksichtigt.Txn2
liest den alten Wert.- Mit
Txn2
werden DatenB
geändert und übernommen.
In diesem Szenario wird jede Transaktion für einen eigenen konsistenten Snapshot der Datenbank ausgeführt, der zum Zeitpunkt des Transaktionsbeginns erstellt wurde. Diese Sequenz kann zu einer Anomalie bei der Schreibabweichung führen, wenn der Schreibvorgang für B
durch Txn2
logisch vom Wert abhängig war, der aus A
gelesen wurde. Im Wesentlichen hat Txn2
seine Aktualisierungen auf der Grundlage veralteter Informationen vorgenommen und der nachfolgende Schreibvorgang verstößt möglicherweise gegen eine Invariante auf Anwendungsebene. Um dieses Szenario zu vermeiden, sollten Sie entweder SELECT...FOR UPDATE
für die Isolation von wiederholbaren Lesevorgängen verwenden oder Prüfeinschränkungen in Ihrem Schema erstellen.
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:Das Nichtvorhandensein (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 die Abbrechen-Funktion für einen Lesevorgang aufrufen, wird der Vorgang nicht 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 Stornierung 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
Standardmäßig erlaubt Spanner mehreren Clients, gleichzeitig mit derselben Datenbank zu interagieren, und zwar mit der standardmäßigen serialisierbaren Isolationsebene. Damit die Datenkonsistenz bei diesen gleichzeitigen Transaktionen gewährleistet wird, verwendet Spanner einen Sperrmechanismus, der sowohl gemeinsame als auch exklusive Sperren nutzt. Diese Lesesperren werden nur für serialisierbare Transaktionen, nicht aber für Transaktionen mit wiederholbarer Leseisolation abgerufen.
Wenn eine serialisierbare Transaktion einen Lesevorgang ausführt, ruft Spanner gemeinsame Lesesperren für die relevanten Daten ab. Diese gemeinsamen Sperren ermöglichen anderen gleichzeitigen Lesevorgängen den Zugriff auf dieselben Daten. 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 ein Konflikt auftritt. - 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 Leistungseinbußen 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 einen einfacheren Ansatz für konsistente Lesevorgänge als sperrende Lese-Schreib-Transaktionen. 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 stamp 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 mit 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 committet werden 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 Zeitstempelabstimmung 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 weniger lange aufrecht.
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, einen speziellen Wert von 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.