Überblick
Auf dieser Seite finden Sie Konzepte zu Abfrageausführungsplänen sowie dazu, wie diese von Spanner zum Ausführen von Abfragen in einer verteilten Umgebung verwendet werden. Informationen zum Abrufen eines Ausführungsplans für eine bestimmte Abfrage über die Google Cloud Console finden Sie unter So werden Abfragen von Spanner ausgeführt. Sie können sich auch Beispiele für bisherige Abfragepläne ansehen und die Leistung einer Abfrage für bestimmte Abfragen über einen bestimmten Zeitraum vergleichen. Weitere Informationen finden Sie unter Beispielbasierte Abfragepläne.
Spanner verwendet deklarative SQL-Anweisungen, um seine Datenbanken abzufragen. SQL-Anweisungen definieren, was der Nutzer möchte, ohne anzugeben, wie die Ergebnisse zu erhalten sind. Ein Abfrageausführungsplan beinhaltet eine Reihe von Schritten, mit denen die Ergebnisse erzielt werden sollen. Bei einer gegebenen SQL-Anweisung kann es mehrere Möglichkeiten geben, die Ergebnisse zu erhalten. Die Abfrageoptimierung von Spanner wertet verschiedene Ausführungspläne aus und wählt den effizientesten aus. Spanner verwendet dann den Ausführungsplan, um die Ergebnisse abzurufen.
Das Konzept eines Abfrageausführungsplans ist eine Struktur relationaler Operatoren. Jeder Operator liest Zeilen aus seinen Eingaben und erzeugt Ausgabezeilen. Das Ergebnis des Operators am Stammverzeichnis der Ausführung wird als Ergebnis der SQL-Abfrage zurückgegeben.
Im Folgenden ist ein Beispiel aufgeführt. Die Abfrage
SELECT s.SongName FROM Songs AS s;
führt zu einem Abfrageausführungsplan, der so visualisiert werden kann:
Die Abfragen und Ausführungspläne auf dieser Seite basieren auf dem folgenden Datenbankschema:
CREATE TABLE Singers (
SingerId INT64 NOT NULL,
FirstName STRING(1024),
LastName STRING(1024),
SingerInfo BYTES(MAX),
BirthDate DATE,
) PRIMARY KEY(SingerId);
CREATE INDEX SingersByFirstLastName ON Singers(FirstName, LastName);
CREATE TABLE Albums (
SingerId INT64 NOT NULL,
AlbumId INT64 NOT NULL,
AlbumTitle STRING(MAX),
MarketingBudget INT64,
) PRIMARY KEY(SingerId, AlbumId),
INTERLEAVE IN PARENT Singers ON DELETE CASCADE;
CREATE INDEX AlbumsByAlbumTitle ON Albums(AlbumTitle);
CREATE INDEX AlbumsByAlbumTitle2 ON Albums(AlbumTitle) STORING (MarketingBudget);
CREATE TABLE Songs (
SingerId INT64 NOT NULL,
AlbumId INT64 NOT NULL,
TrackId INT64 NOT NULL,
SongName STRING(MAX),
Duration INT64,
SongGenre STRING(25),
) PRIMARY KEY(SingerId, AlbumId, TrackId),
INTERLEAVE IN PARENT Albums ON DELETE CASCADE;
CREATE INDEX SongsBySingerAlbumSongNameDesc ON Songs(SingerId, AlbumId, SongName DESC), INTERLEAVE IN Albums;
CREATE INDEX SongsBySongName ON Songs(SongName);
CREATE TABLE Concerts (
VenueId INT64 NOT NULL,
SingerId INT64 NOT NULL,
ConcertDate DATE NOT NULL,
BeginTime TIMESTAMP,
EndTime TIMESTAMP,
TicketPrices ARRAY<INT64>,
) PRIMARY KEY(VenueId, SingerId, ConcertDate);
Sie können die folgenden DML-Anweisungen (Data Manipulation Language) verwenden, um diesen Tabellen Daten hinzuzufügen:
INSERT INTO Singers (SingerId, FirstName, LastName, BirthDate)
VALUES (1, "Marc", "Richards", "1970-09-03"),
(2, "Catalina", "Smith", "1990-08-17"),
(3, "Alice", "Trentor", "1991-10-02"),
(4, "Lea", "Martin", "1991-11-09"),
(5, "David", "Lomond", "1977-01-29");
INSERT INTO Albums (SingerId, AlbumId, AlbumTitle)
VALUES (1, 1, "Total Junk"),
(1, 2, "Go, Go, Go"),
(2, 1, "Green"),
(2, 2, "Forever Hold Your Peace"),
(2, 3, "Terrified"),
(3, 1, "Nothing To Do With Me"),
(4, 1, "Play");
INSERT INTO Songs (SingerId, AlbumId, TrackId, SongName, Duration, SongGenre)
VALUES (2, 1, 1, "Let's Get Back Together", 182, "COUNTRY"),
(2, 1, 2, "Starting Again", 156, "ROCK"),
(2, 1, 3, "I Knew You Were Magic", 294, "BLUES"),
(2, 1, 4, "42", 185, "CLASSICAL"),
(2, 1, 5, "Blue", 238, "BLUES"),
(2, 1, 6, "Nothing Is The Same", 303, "BLUES"),
(2, 1, 7, "The Second Time", 255, "ROCK"),
(2, 3, 1, "Fight Story", 194, "ROCK"),
(3, 1, 1, "Not About The Guitar", 278, "BLUES");
Die Erstellung effizienter Ausführungspläne ist schwierig, da Spanner die Daten in Splits unterteilt. Splits können sich unabhängig voneinander bewegen und verschiedenen Servern zugewiesen werden, die sich an verschiedenen physischen Standorten befinden können. Um Ausführungspläne für die verteilten Daten zu bewerten, verwendet Spanner die Ausführung auf Basis von:
- der lokalen Ausführung von Teilplänen auf Servern, die die Daten enthalten
- der Orchestrierung und Aggregation mehrerer Remote-Ausführungen mit aggressiver Distributionsbereinigung
Spanner verwendet den primitiven Operator distributed union
zusammen mit seinen Varianten distributed cross apply
und distributed outer apply
, um dieses Modell zu aktivieren.
Stichprobenbasierte Abfragepläne
Mit Spanner-Stichprobenabfrageplänen können Sie Beispiele früherer Abfragepläne ansehen und die Leistung einer Abfrage im Zeitverlauf vergleichen. Nicht für alle Abfragen sind Stichproben-Abfragepläne verfügbar. Es können nur Abfragen erfasst werden, die eine höhere CPU-Auslastung verbrauchen. Die Datenaufbewahrung für Beispiele für Spanner-Abfragepläne beträgt 30 Tage. Beispiele für Abfragepläne finden Sie auf der Seite Query Insights der Google Cloud Console. Eine Anleitung finden Sie unter Beispiele für Abfragepläne ansehen.
Der Aufbau eines Stichprobenplans entspricht dem eines Plans für die Ausführung regelmäßiger Abfragen. Weitere Informationen zum Verständnis von visuellen Plänen und deren Verwendung zum Debuggen von Abfragen finden Sie unter Einführung in die Visualisierung des Abfrageplans.
Häufige Anwendungsfälle für Stichproben-Abfragepläne:
Einige häufige Anwendungsfälle für Stichproben-Abfragepläne sind:
- Beobachten Sie Änderungen des Abfrageplans aufgrund von Schemaänderungen (z. B. Hinzufügen oder Entfernen eines Index).
- Beobachten Sie Änderungen des Abfrageplans aufgrund einer Aktualisierung der Optimierungsversion.
- Sie können Änderungen des Abfrageplans aufgrund von neuen Optimierungsstatistiken beobachten. Diese werden alle drei Tage automatisch erfasst oder manuell mit dem Befehl
ANALYZE
ausgeführt.
Wenn die Leistung einer Abfrage im Laufe der Zeit erhebliche Unterschiede zeigt oder Sie die Leistung einer Abfrage verbessern möchten, finden Sie in den Best Practices für SQL optimierte Abfrageanweisungen, mit denen Spanner effiziente Ausführungspläne finden kann.
Phasen einer Abfrage
Eine SQL-Abfrage in Spanner wird zuerst in einen Ausführungsplan kompiliert und dann zur Ausführung an einen ersten Root-Server gesendet. Der Root-Server wird so ausgewählt, dass die Anzahl der Hops minimiert wird, die die abgefragten Daten erreichen. Der Root-Server führt dann folgende Schritte aus:
- Initiierung der Remote-Ausführung von Teilplänen (falls erforderlich)
- Warten auf die Ergebnisse der Remote-Ausführungen
- Verwaltung aller verbleibenden lokalen Ausführungsschritte, z. B. das Aggregieren von Ergebnissen
- Zurückgabe der Ergebnisse für die Abfrage
Remote-Server, die einen Teilplan erhalten, fungieren als "Root"-Server für ihren Teilplan und folgen demselben Modell wie der oberste Root-Server. Das Ergebnis ist eine Struktur von Remote-Ausführungen. Die Abfrageausführung wird konzeptionell von oben nach unten ausgeführt und Abfrageergebnisse werden von unten nach oben zurückgegeben. Das folgende Diagramm veranschaulicht das Muster:
Die folgenden Beispiele veranschaulichen dieses Muster genauer.
Aggregatabfragen
Eine Aggregatabfrage implementiert GROUP BY
-Abfragen.
Wenn Sie zum Beispiel diese Abfrage verwenden:
SELECT s.SingerId, COUNT(*) AS SongCount
FROM Songs AS s
WHERE s.SingerId < 100
GROUP BY s.SingerId;
Dies sind die Ergebnisse:
+----------+-----------+
| SingerId | SongCount |
+----------+-----------+
| 3 | 1 |
| 2 | 8 |
+----------+-----------+
Dies ist das Konzept des Ausführungsplans:
Spanner sendet den Ausführungsplan an einen Stammserver, der die Abfrageausführung koordiniert und die Remoteverteilung von Teilplänen ausführt.
Dieser Ausführungsplan beginnt mit dem Typ Distributed Union, der Teilpläne an Remoteserver verteilt, deren Splits SingerId < 100
erfüllen. Nachdem der Scan der einzelnen Splits abgeschlossen ist, fasst der Operator Stream Aggregate Zeilen zusammen, um die Anzahl für jeden SingerId
zu erhalten. Der Operator Serialize Result archiviert das Ergebnis. Schließlich kombiniert Distributed Union alle Ergebnisse und gibt die Abfrageergebnisse zurück.
Unter Operator Aggregate können Sie mehr über Aggregate erfahren.
Zusammengelegte Join-Abfragen
Verschränkte Tabellen werden gemeinsam mit ihren Zeilen zusammengehöriger Tabellen gespeichert. Ein zusammengelegter Join ist eine Verbindung zwischen verschränkten Tabellen. Zusammengelegte Joins können Leistungsverbesserungen gegenüber Joins bieten, für die Indizes oder Back-Joins erforderlich sind.
Wenn Sie zum Beispiel diese Abfrage verwenden:
SELECT al.AlbumTitle, so.SongName
FROM Albums AS al, Songs AS so
WHERE al.SingerId = so.SingerId AND al.AlbumId = so.AlbumId;
(Bei dieser Abfrage wird davon ausgegangen, dass Songs
mit Albums
verschränkt ist.)
Dies sind die Ergebnisse:
+-----------------------+--------------------------+
| AlbumTitle | SongName |
+-----------------------+--------------------------+
| Nothing To Do With Me | Not About The Guitar |
| Green | The Second Time |
| Green | Starting Again |
| Green | Nothing Is The Same |
| Green | Let's Get Back Together |
| Green | I Knew You Were Magic |
| Green | Blue |
| Green | 42 |
| Terrified | Fight Story |
+-----------------------+--------------------------+
Dies ist der Ausführungsplan:
Dieser Ausführungsplan beginnt mit dem Typ Distributed Union, der Teilpläne an Remoteserver verteilt, die Splits der Tabelle Albums
haben.
Da Songs
eine verschränkte Tabelle von Albums
ist, kann jeder Remoteserver den gesamten Teilplan auf jedem Remoteserver ausführen, ohne dass eine Verknüpfung mit einem anderen Server erforderlich ist.
Der Teilplan enthält einen Cross Apply. Bei jedem Cross Apply wird ein Tabellenscan für die Tabelle Albums
ausgeführt, um SingerId
, AlbumId
und AlbumTitle
abzurufen. Der Cross Apply-Vorgang ordnet dann die Ausgabe des Tabellenscans der Ausgabe eines Indexscans für den Index SongsBySingerAlbumSongNameDesc
zu, wobei ein Filter von SingerId
im Index mit dem SingerId
aus der Ausgabe des Tabellenscans übereinstimmt. Jeder Cross Apply-Vorgang sendet seine Ergebnisse an den Operator Serialize Result, der die AlbumTitle
- und SongName
-Daten serialisiert und an die lokalen Distributed Unions zurückgibt. Die Distributed Union fasst die Ergebnisse der Local Distributed Unions zusammen und gibt sie als Abfrageergebnis zurück.
Index- und Back-Join-Abfragen
Bei dem oben stehenden Beispiel wurde ein Join für zwei Tabellen verwendet, von denen eine mit der anderen verschränkt ist. Ausführungspläne sind komplexer und weniger effizient, wenn zwei Tabellen oder eine Tabelle und ein Index nicht verschränkt sind.
Betrachten Sie einen Index, der mit dem folgenden Befehl erstellt wurde:
CREATE INDEX SongsBySongName ON Songs(SongName)
Verwenden Sie diesen Index in dieser Abfrage:
SELECT s.SongName, s.Duration
FROM Songs@{force_index=SongsBySongName} AS s
WHERE STARTS_WITH(s.SongName, "B");
Dies sind die Ergebnisse:
+----------+----------+
| SongName | Duration |
+----------+----------+
| Blue | 238 |
+----------+----------+
Dies ist der Ausführungsplan:
Der resultierende Ausführungsplan ist kompliziert, weil der Index SongsBySongName
nicht die Spalte Duration
enthält. Damit Sie den Wert Duration
erhalten, muss Spanner die indexierten Ergebnisse per Back-Join mit der Tabelle Songs
verknüpfen. Dies ist ein Join, der aber nicht am selben Standort gespeichert ist, da die Tabelle Songs
und der globale Index SongsBySongName
nicht verschränkt sind. Der resultierende Ausführungsplan ist komplexer als das Beispiel für den gemeinsamen Join, da Spanner Optimierungen durchführt, um die Ausführung zu beschleunigen, wenn Daten nicht am selben Standort gespeichert sind.
Der oberste Operator ist der Operator Distributed Cross Apply. Auf der Eingabeseite dieses Operators befinden sich Batches von Zeilen aus dem Index SongsBySongName
, die das Prädikat STARTS_WITH(s.SongName, "B")
erfüllen. Der Distributed Cross Apply ordnet diese Batches dann Remote-Servern zu, deren Splits die Duration
-Daten enthalten. Die Remote-Server verwenden einen Tabellenscan, um die Spalte Duration
abzurufen.
Der Tabellenscan verwendet den Filter Condition:($Songs_key_TrackId' =
$batched_Songs_key_TrackId)
, der TrackId
aus der Tabelle Songs
mit TrackId
der Zeilen verknüpft, für die ein Batch aus dem Index SongsBySongName
erstellt wurde.
Die Ergebnisse werden in der abschließenden Abfrageantwort zusammengefasst. Die Eingangsseite des Distributed Cross Apply enthält wiederum ein Paar aus Distributed Union/Local Distributed Union, um Zeilen aus dem Index auszuwerten, die das Prädikat STARTS_WITH
erfüllen.
Unten sehen Sie eine leicht abgewandelte Abfrage, bei der die Spalte s.Duration
nicht ausgewählt ist:
SELECT s.SongName
FROM Songs@{force_index=SongsBySongName} AS s
WHERE STARTS_WITH(s.SongName, "B");
Wie in diesem Ausführungsplan gezeigt wird, kann diese Abfrage den Index voll ausnutzen:
Der Ausführungsplan erfordert kein Back-Join, da alle von der Abfrage angeforderten Spalten im Index vorhanden sind.
Nächste Schritte
Informationen zur Abfrageoptimierung von Spanner
Weitere Informationen zum Verwalten des Abfrageoptimierungstools