Lebenszyklus einer Spanner-Abfrage

Kunde

Cloud Spanner unterstützt SQL-Abfragen. Hier eine Beispielabfrage:

SELECT s.SingerId, s.FirstName, s.LastName, s.SingerInfo
FROM Singers AS s
WHERE s.FirstName = @firstName;

Die Formulierung @firstName ist ein Verweis auf einen Abfrageparameter. Sie können einen Abfrageparameter überall verwenden, wo ein Literalwert verwendet werden kann. Es wird dringend empfohlen, Parameter in programmatischen APIs zu verwenden. Die Verwendung von Abfrageparametern hilft, SQL-Injection-Angriffe zu vermeiden. Die daraus resultierenden Abfragen werden eher von verschiedenen serverseitigen Caches profitieren. Weitere Informationen finden Sie unter Caching.

Abfrageparameter müssen auf einen Wert begrenzt werden, wenn die Abfrage ausgeführt wird. Beispiel:

Statement statement =
    Statement.newBuilder("SELECT s.SingerId...").bind("firstName").to("Jimi").build();
try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
 while (resultSet.next()) {
 ...
 }
}

Sobald Spanner einen API-Aufruf empfängt, werden die Abfrage und die gebundenen Parameter analysiert, um zu ermitteln, welcher Spanner-Serverknoten die Abfrage verarbeiten soll. Der Server sendet einen Stream von Ergebniszeilen zurück, die von den Aufrufen an ResultSet.next() genutzt werden.

Ausführung von Abfragen

Die Abfrageausführung beginnt mit dem Eintreffen der Anfrage „Abfrage ausführen“ auf einem beliebigen Spanner-Server. Der Server führt die folgenden Schritte aus:

  • Die Anfrage validieren
  • Den Text der Suchanfrage parsen
  • Eine erste Abfragealgebra generieren
  • Eine optimierte Abfragealgebra generieren
  • Einen ausführbaren Abfrageplan generieren
  • Den Plan ausführen (Berechtigungen überprüfen, Daten lesen, Ergebnisse codieren usw.)

Flussdiagramm Abfrageverarbeitung, das die Client-, Stamm- und Blattserver darstellt

Parsen

Der SQL-Parser analysiert den Abfragetext und wandelt ihn in eine abstrakte Syntaxstruktur um. Es extrahiert die grundlegende Abfragestruktur (SELECT … FROM … WHERE …) und führt syntaktische Prüfungen durch.

Algebra

Das Cloud Spanner-Typsystem kann Skalare, Arrays, Strukturen usw. darstellen. Die Abfragealgebra definiert Operatoren für Tabellenscans, das Filtern, Sortieren/Gruppieren, verschiedene Verknüpfungen, Aggregation und vieles mehr. Die ursprüngliche Abfragealgebra besteht aus der Ausgabe des Parsers. Feldnamenreferenzen in der Strukturansicht werden mit dem Datenbankschema aufgelöst. Dieser Code überprüft auch auf semantische Fehler (z. B. falsche Anzahl von Parametern, nicht übereinstimmende Typen usw.).

Der nächste Schritt ("Abfrageoptimierung") geht von der ersten Algebra aus und erzeugt eine optimalere Algebra. Diese kann einfacher, effizienter oder einfach besser an die Fähigkeiten der Ausführungsengine angepasst sein. Beispielsweise könnte die erste Algebra nur "Join" angeben, wobei die optimierte Algebra "Hash Join" angibt.

Ausführung

Der endgültige ausführbare Abfrageplan wird aus der neu geschriebenen Algebra erstellt. Im Grunde ist der ausführbare Plan eine gerichtete azyklische Grafik von "Iteratoren". Jeder Iterator enthält eine Wertesequenz. Iteratoren verbrauchen eventuell Eingaben, um Ausgaben zu generieren (z. B. Iterator sortieren). Abfragen mit einem einzelnen Split können von einem einzelnen Server ausgeführt werden (von dem, der die Daten enthält). Der Server scannt Bereiche verschiedener Tabellen und führt Joins, Aggregationen und alle anderen von der Abfragealgebra definierten Vorgänge aus.

Abfragen mit mehreren Splits werden in mehrere Teile einbezogen. Ein Teil der Abfrage wird weiterhin auf dem Hauptserver (Stammserver) ausgeführt. Andere partielle Unterabfragen werden an Blattknoten übergeben (die Inhaber der Splits, die gelesen werden). Diese Übergabe kann für komplexe Abfragen rekursiv angewendet werden, was zu einer Baumstruktur von Serverausführungen führt. Alle Server vereinbaren einen Zeitstempel, damit die Abfrageergebnisse ein konsistenter Snapshot der Daten sind. Jeder Blattserver gibt einen Stream von Teilergebnissen zurück. Bei Abfragen mit Aggregation können dies partiell aggregierte Ergebnisse sein. Der Abfrage-Stammserver verarbeitet die Ergebnisse von den Blatt-Servern und führt den Rest des Abfrageplans aus. Weitere Informationen finden Sie unter Abfrage-Ausführungspläne.

Wenn eine Abfrage mehrere Splits umfasst, kann Spanner die Abfrage parallel für die Splits ausführen. Der Grad der Parallelität hängt vom Bereich der Daten ab, die von der Abfrage gescannt werden, vom Ausführungsplan der Abfrage und von der Verteilung der Daten auf die Splits. Spanner legt den maximalen Grad an Parallelität für eine Abfrage automatisch auf Grundlage der Instanzgröße und Instanzkonfiguration (regional oder multiregional) fest, um eine optimale Abfrageleistung zu erzielen und eine CPU-Überlastung zu vermeiden.

Caching

Viele Artefakte der Abfrageverarbeitung werden automatisch im Cache gespeichert und noch einmal für alle nachfolgenden Abfragen verwendet. Dazu gehören Abfragealgebren, ausführbare Abfragepläne usw. Das Caching basiert auf dem Abfragetext, den Namen und Typen der gebundenen Parameter. Aus diesem Grund ist die Verwendung von gebundenen Parametern wie @firstName im obigen Beispiel besser als die Verwendung von Literalwerten im Abfragetext. Erstere können einmal im Cache gespeichert werden und unabhängig vom tatsächlichen Grenzwert wiederverwendet werden. Weitere Informationen finden Sie unter Optimieren der Spanner-Abfrageleistung.

Fehlerbehandlung

Die Methoden executeQuery (oder executeStreamingSql) und streamingRead geben einen Stream von PartialResultSet-Nachrichten zurück. Aus Effizienzgründen kann ein einzelner Zeilen- oder Spaltenwert auf mehrere PartialResultSet-Nachrichten aufgeteilt werden, insbesondere bei großen Datenmengen.

Dieser Stream kann durch vorübergehende Netzwerkfehler, Split-Übergaben oder Serverneustarts unterbrochen werden. Bei der Lastverteilung kann es zu Split-Handoffs kommen und bei Upgrades kann es zu Serverneustarts kommen.

Um diese Unterbrechungen zu verarbeiten, enthält Spanner in einigen PartialResultSet-Nachrichten undurchsichtige resume_token-Strings.

Wichtige Punkte zu resume_token:

  • Nicht jede PartialResultSet enthält eine resume_token.
  • Ein resume_token wird in der Regel nur am Ende einer vollständigen Zeile eingefügt, um einen sicheren Punkt für die Wiederaufnahme zu markieren.
  • PartialResultSet mit einem chunked_value (für große Werte, die auf mehrere Nachrichten aufgeteilt sind) hat erst dann einen resume_token, wenn der gesamte Wert und die gesamte Zeile gesendet wurden.
  • Um einen unterbrochenen Stream fortzusetzen, senden Sie eine neue Anfrage mit dem nicht leeren last received resume_token.

Die Spanner-Clientbibliotheken verwalten diese Pufferung und Wiederherstellung automatisch. Sie stellen vollständige Zeilen aus PartialResultSet-Nachrichten zusammen und verfolgen die neuesten resume_token. Wenn die Verbindung unterbrochen wird, verwendet die Bibliothek das letzte gültige Token, um den Stream neu zu starten. Alle Teildaten, die nach diesem Token empfangen wurden, werden verworfen. So erhalten Sie einen kontinuierlichen, duplikatfreien Stream mit vollständigen Zeilen, auch wenn vorübergehende Fehler auftreten.