Auf dieser Seite werden Transaktionen in Spanner beschrieben. Außerdem werden die nicht schreibgeschützten, schreibgeschützten und partitionierten DML-Transaktionsschnittstellen von 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 Muster der Dateninteraktion konzipiert sind:
Lese-/Schreibvorgänge:Diese Transaktionen werden für Lese- und Schreibvorgänge verwendet, gefolgt von einem Commit. Sie rufen möglicherweise Sperren ab. Wenn sie fehlschlagen, sind Wiederholungsversuche erforderlich. 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-/Schreibtransaktionen erfordern sie keinen Commit-Vorgang oder keine Sperren. Sie werden jedoch möglicherweise angehalten, 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 die Ausführung von DML-Anweisungen im großen Maßstab optimiert, unterliegt jedoch Einschränkungen, um sicherzustellen, dass die Anweisung idempotent und partitionierbar ist, sodass sie unabhängig von anderen Partitionen ausgeführt werden kann. Wenn Sie viele 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
Eine Lese-/Schreibtransaktion besteht aus null oder mehr Lese- oder Abfrageanweisungen, gefolgt von einer Commit-Anfrage. Der Client kann jederzeit vor der Commit-Anfrage eine Rollback-Anfrage senden, um die Transaktion abzubrechen.
Serialisierbare Isolation
Bei Verwendung der standardmäßigen serialisierbaren Isolationsebene mit pessimistic concurrency werden Daten in Lese-/Schreibtransaktionen atomar gelesen, geändert und geschrieben. 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 führen dazu, dass Sperren weniger lange gehalten werden. Dadurch steigt die Wahrscheinlichkeit eines erfolgreichen Commits und die Konflikte werden reduziert. Das liegt daran, dass lange gehaltene Sperren zu Deadlocks und abgebrochenen Transaktionen führen können. Spanner versucht, Lesesperren so lange aktiv zu halten, wie die Transaktion Lesevorgänge ausführt und die Transaktion nicht durch Commit oder Rollback beendet wurde. Wenn der Client über einen längeren Zeitraum inaktiv bleibt, kann Spanner die Sperren der Transaktion freigeben und die Transaktion abbrechen.
Wenn Sie einen Schreibvorgang ausführen möchten, der von einem oder mehreren Lesevorgängen abhängt, verwenden Sie eine 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 aktualisiert wird.
- Wenn Schreibvorgänge von Lesevorgängen abhängen, führen Sie beide in derselben Lese-Schreib-Transaktion aus, auch wenn die Schreibvorgänge nicht ausgeführt werden. Wenn Sie beispielsweise 200 $von Konto A zu Konto B überweisen möchten, aber nur, wenn der 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 die einzelne Lesemethode oder eine schreibgeschützte Transaktion. Im Gegensatz zu Lese-Schreib-Transaktionen werden bei einzelnen Lesevorgängen keine Sperren abgerufen.
Alternativ können Sie die serialisierbare Isolation auch für die Verwendung von optimistischer Parallelität konfigurieren. Bei der optimistischen Nebenläufigkeitserkennung wird davon ausgegangen, dass Konflikte selten sind. Lese- und Abfragevorgänge, auch innerhalb einer Lese-Schreib-Transaktion, werden ohne Sperren ausgeführt. Bei der serialisierbaren Isolation werden Lesevorgänge zum Zeitpunkt des Commits validiert. So wird sichergestellt, dass keine andere gleichzeitig übergebene Transaktion die Daten geändert hat, die zuvor von der Transaktion gelesen wurden.
Isolation durch wiederholbare Lesevorgänge
In Spanner wird die Isolation wiederholbarer Lesevorgänge mithilfe einer Technik namens Snapshot-Isolation implementiert. Die Isolation wiederholbarer Lesevorgänge sorgt dafür, dass alle Lesevorgänge innerhalb einer Transaktion mit der Datenbank konsistent sind, wie sie zu Beginn der Transaktion vorlag. Außerdem wird sichergestellt, dass gleichzeitige Schreibvorgänge für dieselben Daten nur dann erfolgreich sind, wenn keine Konflikte auftreten.
Bei der standardmäßigen optimistischen Parallelität werden keine Sperren bis zum Commit-Zeitpunkt abgerufen, wenn Daten geschrieben werden müssen. Wenn es einen Konflikt mit den geschriebenen Daten oder aufgrund von vorübergehenden Ereignissen in Spanner wie einem Serverneustart gibt, kann Spanner Transaktionen trotzdem abbrechen. Da Lesevorgänge in Lese-/Schreibtransaktionen keine Sperren in der Isolation wiederholbarer Lesevorgänge erhalten, gibt es keinen Unterschied zwischen der Ausführung schreibgeschützter Vorgänge innerhalb einer schreibgeschützten Transaktion oder einer Lese-/Schreibtransaktion.
Erwägen Sie die Verwendung von Lese-/Schreibtransaktionen mit der Isolationsstufe „Wiederholbares Lesen“ in den folgenden Szenarien:
- Die Arbeitslast ist leselastig und es gibt nur wenige Schreibkonflikte.
- In der Anwendung treten Leistungsengpässe aufgrund von Verzögerungen durch Sperrenkonflikte und Transaktionsabbrüche auf, die dadurch verursacht werden, dass ältere Transaktionen mit höherer Priorität neuere Transaktionen mit niedrigerer Priorität abbrechen, um potenzielle Deadlocks zu verhindern (Wound-Wait).
- Die Anwendung erfordert nicht die strengeren Garantien, die von der Isolationsebene „Serializable“ bereitgestellt werden.
Wenn Sie einen Schreibvorgang ausführen, der von einem oder mehreren Lesevorgängen abhängt, ist bei der Isolation mit wiederholbarem Lesen eine Schreibabweichung möglich. Schreibabweichungen entstehen durch eine bestimmte Art von gleichzeitiger Aktualisierung, bei der jede Aktualisierung unabhängig akzeptiert wird, ihre kombinierte Wirkung jedoch die Datenintegrität der Anwendung verletzt.
Daher sollten Sie Lesevorgänge, die Teil des kritischen Abschnitts einer Transaktion sind, entweder mit einer FOR UPDATE-Klausel oder einem lock_scanned_ranges=exclusive-Hinweis ausführen, um Schreibabweichungen zu vermeiden. Weitere Informationen finden Sie unter Lese-/Schreibkonflikte und Richtigkeit und im Beispiel unter Lese-/Schreibsemantik.
Schnittstelle
Die Spanner-Clientbibliotheken bieten eine Schnittstelle zum Ausführen eines Arbeitsablaufs innerhalb einer Lese-/Schreibtransaktion mit Wiederholungen für Transaktionsabbrüche. Für eine 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.
Alle Lese-/Schreibtransaktionen bieten die ACID-Attribute von relationalen Datenbanken.
Da Transaktionen atomar sind, hat eine abgebrochene Transaktion keine Auswirkungen auf die Datenbank. Spanner-Clientbibliotheken wiederholen solche Transaktionen automatisch. Wenn Sie die Clientbibliotheken jedoch nicht verwenden, wiederholen Sie die 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 durch erneutes Ausführen der Transaktion maskiert.
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, 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.
Die folgenden Clientbibliotheksbeispiele zeigen, wie eine Lese-/Schreibtransaktion mit der standardmäßigen serialisierbaren Isolationsstufe ausgeführt wird:
C++
C#
Go
Java
Node.js
PHP
Python
Ruby
Beispiele für die Ausführung einer Lese-/Schreibtransaktion mit der Isolationsebene „Wiederholbarer Lesevorgang“ finden Sie unter Isolationsebene „Wiederholbarer Lesevorgang“ verwenden.
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 Clients die strengsten Garantien bezüglich der Nebenläufigkeitserkennung für Transaktionen, nämlich externe Konsistenz. Bei einer Lese-Schreib-Transaktion wird eine Reihe von Lese- und Schreibvorgängen atomar ausgeführt. Schreibvorgänge können fortgesetzt werden, ohne dass sie durch schreibgeschützte Transaktionen blockiert werden. Der Zeitstempel, zu dem Lese-/Schreibtransaktionen ausgeführt werden, entspricht der verstrichenen Zeit. Die Serialisierungsreihenfolge entspricht dieser Zeitstempelreihenfolge.
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.
Sie können Ihre Lese-Schreib-Transaktionen auch mit der Isolation „Repeatable Read“ ausführen. Die Isolation wiederholbarer Lesevorgänge sorgt dafür, dass alle Lesevorgänge innerhalb einer Transaktion einen konsistenten starken Snapshot der Datenbank sehen, wie er zu Beginn der Transaktion vorlag. Weitere Informationen finden Sie unter Isolation wiederholbarer Lesevorgänge.
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 Isolation wiederholbarer Lesevorgänge
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 committet, 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 nach dem Commit der Transaktion.
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. Das erneute Lesen 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, insbesondere bei Verwendung der serialisierbaren Isolation.
Unterschied zwischen Serialisierbarkeit und externer Konsistenz und wiederholbarem Lesen
Cloud Spanner bietet standardmäßig starke Transaktionsgarantien, einschließlich Serialisierbarkeit und externer Konsistenz. Diese Eigenschaften sorgen dafür, dass 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. Cloud Spanner erreicht dies, indem Transaktionen Commit-Zeitstempel zugewiesen werden, die der Reihenfolge entsprechen, in der sie übernommen wurden.
Cloud 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
Txn1werden DatenAgelesen, ein Schreibvorgang fürAwird vorbereitet und dann erfolgreich übergeben. - Transaktion
Txn2beginnt nach dem Start vonTxn1. Zuerst werden DatenBund dann DatenAgelesen.
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 seine Schreibvorgänge in 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
Txn1scheinen zu einem einzigen Zeitpunkt,c1, erfolgt zu sein. - Alle Lese- und Schreibvorgänge in
Txn2scheinen zu einem einzigen Zeitpunkt,c2, erfolgt zu sein. - Wichtig ist, dass
c1für übergebene Schreibvorgänge vorc2liegt, auch wenn die Schreibvorgänge auf verschiedenen Geräten ausgeführt wurden. WennTxn2nur Lesevorgänge ausführt, istc1frü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 „Wiederholbares Lesen“ verwenden, tritt für dieselben Transaktionen das folgende Szenario ein:
Txn1beginnt mit dem Lesen von DatenAund erstellt einen eigenen Snapshot der Datenbank zu diesem Zeitpunkt.Txn2beginnt dann mit dem Lesen der DatenBund erstellt einen eigenen Snapshot.- Als Nächstes ändert
Txn1die DatenAund führt die Änderungen erfolgreich aus. Txn2Versuche, Daten zu lesenA. DaTxn2auf einem früheren Snapshot basiert, wird die AktualisierungTxn1, die gerade fürAvorgenommen wurde, nicht berücksichtigt.Txn2liest den alten Wert.- Mit
Txn2werden DatenBgeändert und übernommen.
In diesem Szenario wird jede Transaktion mit einem 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.
Spanner führt die Vorgänge einer Transaktion möglicherweise intern mehrmals aus. Wenn ein Ausführungsversuch fehlschlägt, gibt der zurückgegebene Fehler die aufgetretenen Bedingungen an und damit, welche Garantien Sie erhalten. Wenn Spanner Ihre Transaktion jedoch wiederholt, können alle Nebeneffekte der zugehörigen Vorgänge (z. B. Änderungen an externen Systemen oder an einem Systemstatus außerhalb einer Spanner-Datenbank) mehrmals auftreten.
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 der gepufferten 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 Daten, die während der Transaktion gelesen werden, 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 dass sich dies auf andere laufende Operationen innerhalb derselben Transaktion auswirkt. 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 Kündigung 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.
Durch das Abbrechen eines Commit-Vorgangs wird die gesamte Transaktion abgebrochen, es sei denn, die Transaktion wurde bereits übergeben oder ist aus einem anderen Grund fehlgeschlagen.
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 wahren. 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 die Änderungen dauerhaft in der Datenbank gespeichert und bleiben auch bei Systemausfällen, Stromausfällen oder anderen Störungen erhalten.
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 abgerufen, nicht für Transaktionen, die die Isolation wiederholbarer Lesevorgänge verwenden.
Wenn eine serialisierbare Transaktion einen Lesevorgang ausführt, ruft Spanner gemeinsame Lesesperren für die relevanten Daten ab. Diese gemeinsamen Sperren ermöglichen es anderen gleichzeitigen Lesevorgängen, auf dieselben Daten zuzugreifen. Diese Parallelität wird beibehalten, bis die Transaktion ihre Änderungen committet.
Bei der serialisierbaren Isolation versucht die Transaktion während der Commit-Phase, wenn Schreibvorgänge angewendet werden, ihre Sperren auf exklusive Sperren zu aktualisieren. Dazu geht Spanner so vor:
- 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 den alleinigen Zugriff auf die Daten für die Dauer des Schreibvorgangs ermöglicht.
Beim Committen einer Transaktion mit wiederholbarer Leseisolation werden exklusive Sperren für die geschriebenen Daten abgerufen. Die Transaktion muss möglicherweise auf Sperren warten, wenn eine gleichzeitige Transaktion auch Schreibvorgänge für dieselben Daten ausführt.
Hinweise zu Sperren:
- Granularität:In Spanner werden Sperren auf Zeilen- und Spaltenebene angewendet. Wenn die Transaktion
T1eine Sperre für die SpalteAder Zeilealbumidenthält, kann die TransaktionT2weiterhin gleichzeitig in die SpalteBderselben Zeilealbumidschreiben, ohne dass ein Konflikt auftritt. Schreibvorgänge ohne Lesevorgänge:
- Wenn die Transaktion keine Lesevorgänge enthält, ist für Schreibvorgänge ohne Lesevorgänge möglicherweise keine exklusive Sperre erforderlich. Stattdessen wird möglicherweise 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.
- Bei der Isolation „Repeatable Read“ erhalten Transaktionen in der Regel exklusive Sperren für geschriebene Zellen zum Zeitpunkt des Commits.
Sekundäre Indexe für Zeilensuchen:Bei der serialisierbaren Isolation kann die Verwendung sekundärer Indexe die Leistung beim Lesen innerhalb einer Lese-/Schreibtransaktion erheblich verbessern. Wenn Sie sekundäre Indexe verwenden, um die Anzahl der durchsuchten 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
Cloud Spanner kann Transaktionen für Daten ausführen, die sich über mehrere Server erstrecken. Diese Funktion führt jedoch zu Leistungskosten, 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. Cloud 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-Stempel bereinigt wird. Die Standardrichtlinie für die automatische Speicherbereinigung von Spanner ist jedoch in der Regel 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.commitodersessions.rollbackerforderlich 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 werden garantiert die Auswirkungen aller Transaktionen berücksichtigt, die vor Beginn des Lesevorgangs mit einem Commit festgeschrieben 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.
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 von Änderungsstreams
Ein Änderungsstream ist ein Schemaobjekt, das Sie so konfigurieren können, dass Datenänderungen in einer gesamten Datenbank, in bestimmten Tabellen oder in einem definierten Satz 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.