Lebensdauer einer Spanner-Abfrage

Client

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. Siehe Caching unten.

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, analysiert es die Abfrage und die gebundenen Parameter, um zu bestimmen, 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 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 Typsystem von Spanner kann Skalare, Arrays, Strukturen usw. darstellen. Die Abfragealgebra definiert Operatoren für Tabellenscans, Filtern, Sortieren/Gruppieren, alle Arten von Joins, Aggregation und vieles mehr. Die ursprüngliche Abfragealgebra besteht aus der Ausgabe des Parsers. Feldnamenreferenzen in der Strukturansicht werden mit dem Datenbankschema gelö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 Abfrageausführungspläne.

Wenn eine Abfrage mehrere Splits umfasst, kann Spanner die Abfrage über die Splits parallel ausführen. Der Grad der Parallelität hängt vom Datenbereich, den die Abfrage scannt, vom Abfrageausführungsplan und der Verteilung der Daten über Splits ab. Spanner legt automatisch den maximalen Parallelitätsgrad für eine Abfrage anhand der Instanzgröße und der Instanzkonfiguration (regional oder multiregional) fest, um eine optimale Abfrageleistung zu erzielen und eine Überlastung der CPU 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 und so weiter. 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 Spanner-Abfrageleistung optimieren.

Fehlerbehandlung

Der Stream von Ergebniszeilen aus der Methode executeQuery kann aus verschiedenen Gründen unterbrochen werden: vorübergehende Netzwerkfehler, Übergabe eines Splits von einem Server an einen anderen (z.B. Load-Balancing), Serverneustarts (z.B. Upgrade auf eine neue Version) usw. Zur Behebung dieser Fehler sendet Spanner intransparente „Fortsetzen“-Tokens zusammen mit Batches von Teilergebnisdaten. Diese Lebenslauftokens können verwendet werden, wenn die Abfrage wiederholt wird, um dort fortzufahren, wo die unterbrochene Abfrage aufgehört hat. Wenn Sie die Spanner-Clientbibliotheken verwenden, erfolgt dies automatisch. Nutzer der Clientbibliothek müssen sich daher keine Gedanken über diese Art von vorübergehenden Fehlern machen.