Questa pagina descrive in dettaglio gli operatori utilizzati nei piani di esecuzione delle query di Spanner. Per scoprire come recuperare un piano di esecuzione per una query specifica utilizzando la console Google Cloud , consulta Informazioni su come Spanner esegue le query.
Le query e i piani di esecuzione in questa pagina si basano sul seguente schema del database:
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);
Puoi utilizzare le seguenti istruzioni DML (Data Manipulation Language) per aggiungere dati a queste tabelle:
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");
Operatori foglia
Un operatore foglia è un operatore che non ha elementi secondari. I tipi di operatori foglia sono:
Estrai array
Un operatore di estrazione di array appiattisce un array di input in righe di elementi. Ogni riga risultante contiene massimo due colonne: il valore effettivo dell'array e, facoltativamente, la posizione in base zero nell'array.
Ad esempio, utilizzando questa query:
SELECT a, b FROM UNNEST([1,2,3]) a WITH OFFSET b;
La query appiattisce l'array [1,2,3] nella colonna a e mostra la posizione dell'array nella colonna b.
Ecco i risultati:
| a | b |
|---|---|
| 1 | 0 |
| 2 | 1 |
| 3 | 2 |
Questo è il piano di esecuzione:
Genera relazione
Un operatore generate relation restituisce zero o più righe.
Relazione con unità
La relazione unità restituisce una riga. Si tratta di un caso speciale dell'operatore generate relation.
Ad esempio, utilizzando questa query:
SELECT 1 + 2 AS Result;
Il risultato è:
| Risultato |
|---|
| 3 |
Questo è il piano di esecuzione:
Relazione vuota
La relazione vuota non restituisce righe. Si tratta di un caso speciale dell'operatore generate relation.
Ad esempio, utilizzando questa query:
SELECT *
FROM albums
LIMIT 0
Il risultato è:
Nessun risultato
Questo è il piano di esecuzione:
Scansione
Un operatore di scansione restituisce le righe eseguendo la scansione di un'origine di righe. Questi sono i tipi di operatori di scansione:
- Scansione tabella: la scansione viene eseguita su una tabella.
- Scansione dell'indice: la scansione viene eseguita su un indice.
- Scansione batch: la scansione viene eseguita su tabelle intermedie create da altri operatori relazionali (ad esempio, una tabella creata da un cross-apply distribuito).
Quando possibile, Spanner applica i predicati alle chiavi nell'ambito di
una scansione. Le scansioni vengono eseguite in modo più efficiente quando vengono applicati i predicati perché la
scansione non deve leggere l'intera tabella o l'intero indice. I predicati vengono visualizzati nel piano di esecuzione nel formato KeyPredicate: column=value.
Nel peggiore dei casi, una query potrebbe dover cercare tutte le righe di una tabella. Questa
situazione porta a una scansione completa e viene visualizzata nel piano di esecuzione come full scan:
true.
Ad esempio, utilizzando questa query:
SELECT s.lastname
FROM singers@{FORCE_INDEX=SingersByFirstLastName} as s
WHERE s.firstname = 'Catalina';
Ecco i risultati:
| Cognome |
|---|
| Smith |
Questo è il piano di esecuzione:
Nel piano di esecuzione, l'operatore distributed union di primo livello invia i sottopiani ai server remoti. Ogni piano secondario ha un operatore serialize
result e un operatore di scansione dell'indice. Il predicato
Key Predicate: FirstName = 'Catalina' limita la scansione alle righe dell'indice
SingersByFirstLastname che hanno FirstName uguale a Catalina. L'output
della scansione dell'indice viene restituito all'operatore di serializzazione dei risultati.
Operatori unari
Un operatore unario è un operatore che ha un solo elemento secondario relazionale.
I seguenti operatori sono operatori unari:
- Aggregate
- Applicare le mutazioni
- Crea batch
- Computing
- Compute struct
- DataBlockToRowAdapter
- Filtro
- Scansione dei filtri
- Limite
- Unione della divisione locale
- Random Id Assign
- RowToDataBlockAdapter
- Serialize result
- Ordina
- TVF
- Union input
Aggregazione
Un operatore aggregato implementa istruzioni SQL GROUP BY e funzioni di aggregazione (come COUNT). L'input per un operatore aggregato viene partizionato logicamente in gruppi disposti in colonne chiave (o in un singolo gruppo se GROUP BY non è presente). Per ogni gruppo vengono calcolati zero o più aggregazioni.
Ad esempio, utilizzando questa query:
SELECT s.singerid,
Avg(s.duration) AS average,
Count(*) AS count
FROM songs AS s
GROUP BY singerid;
La query raggruppa per SingerId ed esegue un'aggregazione AVG e un'aggregazione COUNT.
Ecco i risultati:
| SingerId | media | conteggio |
|---|---|---|
| 3 | 278 | 1 |
| 2 | 225.875 | 8 |
Questo è il piano di esecuzione:
Gli operatori di aggregazione possono essere basati sul flusso o basati sull'hash. Il piano di esecuzione
precedente mostra un aggregato basato sul flusso. Gli aggregati basati su stream leggono da
input già preordinati (se è presente GROUP BY) e calcolano il gruppo
senza blocchi. Gli aggregati basati su hash creano tabelle hash per mantenere
aggregati incrementali di più righe di input contemporaneamente. Gli aggregati basati su stream sono più veloci e utilizzano meno memoria rispetto agli aggregati basati su hash, ma richiedono che l'input sia ordinato (in base alle colonne chiave o agli indici secondari).
Per gli scenari distribuiti, un operatore di aggregazione può essere separato in una coppia locale-globale. Ogni server remoto esegue l'aggregazione locale sulle proprie righe di input e poi restituisce i risultati al server radice. Il server radice esegue l'aggregazione globale.
Applica mutazioni
Un operatore apply mutations applica le mutazioni di un'istruzione DML (Data Manipulation Statement) alla tabella. È l'operatore principale in un piano di query per un'istruzione DML.
Ad esempio, utilizzando questa query:
DELETE FROM singers
WHERE firstname = 'Alice';
Ecco i risultati:
4 rows deleted This statement deleted 4 rows and did not return any rows.
Questo è il piano di esecuzione:
Creazione batch
Un operatore create batch raggruppa le righe di input in una sequenza. Un'operazione di creazione batch si verifica di solito nell'ambito di un'operazione di applicazione incrociata distribuita. Le righe di input possono essere riordinate durante il batch. Il numero di righe di input raggruppate in batch in ogni esecuzione dell'operatore batch varia.
Consulta l'operatore cross-apply distribuito per un esempio di operatore di creazione batch in un piano di esecuzione.
Computing
Un operatore compute produce output leggendo le righe di input e aggiungendo una o più colonne aggiuntive calcolate utilizzando espressioni scalari. Consulta l'operatore Union All per un esempio di operatore di calcolo in un piano di esecuzione.
Compute struct
Un operatore compute struct crea una variabile per una struttura che contiene campi per ciascuna delle colonne di input.
Ad esempio, utilizzando questa query:
SELECT FirstName,
ARRAY(SELECT AS STRUCT song.SongName, song.SongGenre
FROM Songs AS song
WHERE song.SingerId = singer.SingerId)
FROM singers AS singer
WHERE singer.SingerId = 3;
Ecco i risultati:
| Nome | Non specificato |
|---|---|
| Alice | [["Not About The Guitar","BLUES"]] |
Questo è il piano di esecuzione:
Nel piano di esecuzione, l'operatore di sottoquery array riceve l'input da un operatore
distributed union, che riceve l'input da un operatore
compute struct. L'operatore di struttura di calcolo crea una struttura dalle colonne SongName e SongGenre nella tabella Songs.
DataBlockToRowAdapter
Un operatore DataBlockToRowAdapter viene inserito automaticamente dall'ottimizzatore di query Spanner tra una coppia di operatori che operano utilizzando metodi di esecuzione diversi. Il suo input è un operatore che utilizza il metodo di esecuzione orientato ai batch e il suo output viene inserito in un operatore che esegue il metodo di esecuzione orientato alle righe. Per maggiori informazioni, vedi Ottimizzare l'esecuzione
delle query.
Filtro
Un operatore filter legge tutte le righe dell'input, applica un predicato scalare a ogni riga e restituisce solo le righe che soddisfano il predicato.
Ad esempio, utilizzando questa query:
SELECT s.lastname
FROM (SELECT s.lastname
FROM singers AS s
LIMIT 3) s
WHERE s.lastname LIKE 'Rich%';
Ecco i risultati:
| Cognome |
|---|
| Richards |
Questo è il piano di esecuzione:
Il predicato per i cantanti il cui cognome inizia con Rich è implementato come
filtro. L'input del filtro è l'output di una scansione dell'indice e
l'output del filtro sono le righe in cui LastName inizia con Rich.
Per migliorare il rendimento, ogni volta che un filtro viene posizionato direttamente sopra una scansione,
il filtro influisce sulla modalità di lettura dei dati. Ad esempio, considera una tabella con la chiave k.
Un filtro con il predicato k = 5 direttamente sopra una scansione della tabella cerca
righe che corrispondono a k = 5, senza leggere l'intero input. Ciò comporta un'esecuzione più efficiente della query. Nell'esempio precedente, l'operatore di filtro
legge solo le righe che soddisfano il predicato WHERE s.LastName LIKE 'Rich%'.
Scansione del filtro
Un operatore di scansione del filtro si trova sempre in cima a una scansione di tabella o indice. Funziona con la scansione per ridurre il numero di righe lette dal database e la scansione risultante è in genere più veloce rispetto a un filtro. Spanner applica la scansione del filtro in determinate condizioni:
- Condizione di ricerca: la condizione di ricerca si applica se Spanner
può determinare una riga specifica a cui accedere nella tabella. In generale, questo
si verifica quando il filtro si trova su un prefisso della chiave primaria. Ad esempio, se la chiave primaria è costituita da
Col1eCol2, una clausolaWHEREche include valori espliciti perCol1oCol1eCol2è ricercabile. In questo caso, Spanner legge i dati solo all'interno dell'intervallo di chiavi. - Condizione residua: qualsiasi altra condizione in cui Spanner può valutare la scansione per limitare la quantità di dati letti.
Ad esempio, utilizzando questa query:
SELECT lastname
FROM singers
WHERE singerid = 1
Ecco i risultati:
| Cognome |
|---|
| Richards |
Questo è il piano di esecuzione:
Limite
Un operatore limit vincola il numero di righe restituite. Un parametro facoltativo OFFSET
specifica la riga iniziale da restituire. Per gli scenari distribuiti, un operatore di limite può essere suddiviso in una coppia locale-globale. Ogni server remoto
applica il limite locale alle proprie righe di output, quindi restituisce i risultati al
server radice. Il server radice aggrega le righe inviate dai server remoti e
poi applica il limite globale.
Ad esempio, utilizzando questa query:
SELECT s.songname
FROM songs AS s
LIMIT 3;
Ecco i risultati:
| SongName |
|---|
| Not About The Guitar |
| La seconda volta |
| Ricominciare |
Questo è il piano di esecuzione:
Il limite locale è il limite per ogni server remoto. Il server radice aggrega le righe dai server remoti e poi applica il limite globale.
Assegnazione ID casuale
Un operatore assegnazione ID casuale produce l'output leggendo le righe di input e
aggiungendo un numero casuale a ogni riga. Funziona con un operatore Filter o Sort
per ottenere metodi di campionamento. I metodi di campionamento supportati sono
Bernoulli
e Reservoir.
Ad esempio, la seguente query utilizza il campionamento di Bernoulli con una frequenza di campionamento del 10%.
SELECT s.songname
FROM songs AS s TABLESAMPLE bernoulli (10 PERCENT);
Ecco i risultati:
| SongName |
|---|
| Ricominciare |
| Niente è uguale |
Tieni presente che, poiché il risultato è un campione, potrebbe variare ogni volta che viene eseguita la query, anche se la query è la stessa.
Questo è il piano di esecuzione:
In questo piano di esecuzione, l'operatore Random Id Assign riceve l'input da
un operatore distributed union, che riceve l'input
da una scansione dell'indice. L'operatore restituisce le righe con ID casuali e
l'operatore Filter applica quindi un predicato scalare agli ID casuali e
restituisce circa il 10% delle righe.
L'esempio seguente utilizza Reservoir
campionamento con una frequenza di campionamento di 2 righe.
SELECT s.songname
FROM songs AS s TABLESAMPLE reservoir (2 rows);
Ecco i risultati:
| SongName |
|---|
| I Knew You Were Magic |
| La seconda volta |
Tieni presente che, poiché il risultato è un campione, potrebbe variare ogni volta che viene eseguita la query, anche se la query è la stessa.
Questo è il piano di esecuzione:
In questo piano di esecuzione, l'operatore Random Id Assign riceve l'input da
un operatore distributed union, che riceve l'input
da una scansione dell'indice. L'operatore restituisce le righe con ID casuali e
l'operatore Sort applica l'ordinamento agli ID casuali e applica
LIMIT con 2 righe.
Sindacato locale
Un operatore unione suddivisione locale trova le suddivisioni della tabella archiviate sul server locale, esegue una sottoquery su ogni suddivisione e poi crea un'unione che combina tutti i risultati.
Una unione di suddivisione locale viene visualizzata nei piani di esecuzione che analizzano una tabella di posizionamento. I posizionamenti possono aumentare il numero di suddivisioni in una tabella, rendendo più efficiente la scansione delle suddivisioni in batch in base alle loro posizioni di archiviazione fisica.
Ad esempio, supponiamo che la tabella Singers utilizzi una chiave di posizionamento per partizionare
i dati dei cantanti:
CREATE TABLE Singers (
SingerId INT64 NOT NULL,
SingerName STRING(MAX) NOT NULL,
...
Location STRING(MAX) NOT NULL PLACEMENT KEY
) PRIMARY KEY (SingerId);
Ora considera questa query:
SELECT BirthDate FROM Singers;
Questo è il piano di esecuzione:
L'unione distribuita invia una sottoquery a ogni batch di
divisioni memorizzate fisicamente insieme nello stesso server. Su ogni server, l'unione
delle suddivisioni locali trova le suddivisioni che archiviano i dati Singers, esegue la sottoquery su ogni
suddivisione e restituisce i risultati combinati. In questo modo, l'unione distribuita e
l'unione locale suddivisa funzionano insieme per eseguire la scansione in modo efficiente della tabella Singers. Senza
un'operazione Union suddivisa locale, l'operazione Union distribuita invierebbe una RPC per suddivisione anziché
per batch di suddivisioni, con conseguenti round trip RPC ridondanti quando c'è più
di una suddivisione per batch.
RowToDataBlockAdapter
Un operatore RowToDataBlockAdapter viene inserito automaticamente dall'ottimizzatore di query Spanner tra una coppia di operatori che operano utilizzando metodi di esecuzione diversi. Il suo input è un operatore che utilizza il metodo di esecuzione orientato alle righe e il suo output viene inserito in un operatore che esegue il metodo di esecuzione orientato ai batch. Per saperne di più, consulta Ottimizzare l'esecuzione delle query.
Serializza risultato
Un operatore serialize result è un caso speciale dell'operatore compute struct che serializza ogni riga del risultato finale della query per restituirla al client.
Ad esempio, utilizzando questa query:
SELECT array
(
select as struct so.songname,
so.songgenre
FROM songs AS so
WHERE so.singerid = s.singerid)
FROM singers AS s;
La query richiede un array di SongName e SongGenre in base a SingerId.
Ecco i risultati:
| Non specificato |
|---|
| [] |
| [[Let's Get Back Together, COUNTRY], [Starting Again, ROCK]] |
| [[Not About The Guitar, BLUES]] |
| [] |
| [] |
Questo è il piano di esecuzione:
L'operatore di serializzazione dei risultati crea un risultato che contiene, per ogni riga della
tabella Singers, un array di coppie SongName e SongGenre per i brani
del cantante.
Ordina
Un operatore sort legge le righe di input, le ordina per colonna o colonne e poi restituisce i risultati ordinati.
Ad esempio, utilizzando questa query:
SELECT s.songgenre
FROM songs AS s
ORDER BY songgenre;
Ecco i risultati:
| SongGenre |
|---|
| BLUES |
| BLUES |
| BLUES |
| BLUES |
| CLASSICA |
| PAESE |
| ROCK |
| ROCK |
| ROCK |
Questo è il piano di esecuzione:
In questo piano di esecuzione, l'operatore di ordinamento riceve le righe di input da un operatore distributed union, le ordina e le restituisce a un operatore serialize result.
Per limitare il numero di righe restituite, un operatore di ordinamento può facoltativamente avere i parametri
LIMIT e OFFSET. Per gli scenari distribuiti, un operatore di ordinamento con un operatore LIMIT o OFFSET viene separato in una coppia locale-globale. Ogni server remoto applica l'ordinamento e il limite o l'offset locale alle proprie righe di input, quindi restituisce i risultati al server radice. Il server radice
aggrega le righe inviate dai server remoti, le ordina e poi applica il
limite/offset globale.
Ad esempio, utilizzando questa query:
SELECT s.songgenre
FROM songs AS s
ORDER BY songgenre
LIMIT 3;
Ecco i risultati:
| SongGenre |
|---|
| BLUES |
| BLUES |
| BLUES |
Questo è il piano di esecuzione:
Il piano di esecuzione mostra il limite locale per i server remoti e il limite globale per il server radice.
TVF
Un operatore funzione con valori di tabella produce l'output leggendo le righe di input e applicando la funzione specificata. La funzione potrebbe implementare la mappatura e restituire lo stesso numero di righe dell'input. Può anche essere un generatore che restituisce più righe o un filtro che restituisce meno righe.
Ad esempio, utilizzando questa query:
SELECT genre,
songname
FROM ml.predict(model genreclassifier, TABLE songs)
Ecco i risultati:
| Genere | SongName |
|---|---|
| Paese | Not About The Guitar |
| Rock | La seconda volta |
| Pop | Ricominciare |
| Pop | Niente è uguale |
| Paese | Torniamo insieme |
| Pop | I Knew You Were Magic |
| Elettronica | Blu |
| Rock | 42 |
| Rock | Fight Story |
Questo è il piano di esecuzione:
Input dell'unione
Un operatore Union Input restituisce i risultati a un operatore Union All. Consulta l'operatore Union All per un esempio di operatore di input Union in un piano di esecuzione.
Operatori binari
Un operatore binario è un operatore che ha due figli relazionali. I seguenti operatori sono operatori binari:
- Applicazione incrociata
- Hash join
- Unione di tipo Merge
- Push broadcast hash join
- Outer apply
- Unione ricorsiva
Cross apply
Un operatore Cross Apply esegue una query di tabella su ogni riga recuperata da una query di un'altra tabella e restituisce l'unione di tutte le esecuzioni di query di tabella. Gli operatori Cross Apply e Outer Apply eseguono l'elaborazione orientata alle righe, a differenza degli operatori che eseguono l'elaborazione basata su set, come Hash Join . L'operatore Cross Apply ha due input, input e map. L'operatore Cross Apply applica ogni riga del lato input al lato mappa. Il risultato dell'applicazione incrociata ha colonne sia dal lato dell'input che da quello della mappa.
Ad esempio, utilizzando questa query:
SELECT si.firstname,
(SELECT so.songname
FROM songs AS so
WHERE so.singerid = si.singerid
LIMIT 1)
FROM singers AS si;
La query chiede il nome di ogni cantante, insieme al nome di una sola delle sue canzoni.
Ecco i risultati:
| Nome | Non specificato |
|---|---|
| Alice | Not About The Guitar |
| Catalina | Torniamo insieme |
| Davide | NULL |
| Lea | NULL |
| Marc | NULL |
La prima colonna viene compilata dalla tabella Singers, mentre la seconda dalla tabella Songs. Nei casi in cui esisteva un SingerId nella
tabella Singers ma non c'era un SingerId corrispondente nella tabella Songs, la
seconda colonna contiene NULL.
Questo è il piano di esecuzione:
Il nodo di primo livello è un operatore distributed union. L'operatore distributed union distribuisce i piani secondari ai server remoti. Il piano secondario contiene un operatore serialize result che calcola il nome del cantante e il nome di una delle sue canzoni e serializza ogni riga dell'output.
L'operatore di serializzazione dei risultati riceve l'input da un operatore di applicazione incrociata.
Il lato di input dell'operatore Cross Apply è una scansione della tabella
Singers.
Il lato della mappa per l'operazione di applicazione incrociata contiene quanto segue (dall'alto verso il basso):
- Un operatore aggregato che restituisce
Songs.SongName. - Un operatore limit che limita il numero di brani restituiti a uno per cantante.
- Una scansione dell'indice sull'indice
SongsBySingerAlbumSongNameDesc.
L'operatore Cross Apply mappa ogni riga del lato input a una riga del lato mappa
che ha lo stesso SingerId. L'output dell'operatore cross apply è il valore FirstName della riga di input e il valore SongName della riga della mappa.
(Il valore di SongName è NULL se non esiste una riga della mappa che corrisponda a SingerId.) L'operatore Union distribuito nella parte superiore del piano di esecuzione combina tutte le righe di output dei server remoti e le restituisce come risultati della query.
Hash join
Un operatore hash join è un'implementazione basata su hash dei join SQL. I join hash eseguono l'elaborazione basata su set. L'operatore di hash join legge le righe dell'input contrassegnate come build e le inserisce in una tabella hash in base a una condizione di join. L'operatore di hash join legge quindi le righe dell'input contrassegnato come probe. Per ogni riga letta dall'input della sonda, l'operatore di hash join cerca le righe corrispondenti nella tabella hash. L'operatore di hash join restituisce le righe corrispondenti come risultato.
Ad esempio, utilizzando questa query:
SELECT a.albumtitle,
s.songname
FROM albums AS a join@{join_method=hash_join} songs AS s
ON a.singerid = s.singerid
AND a.albumid = s.albumid;
Ecco i risultati:
| AlbumTitle | SongName |
|---|---|
| Niente a che fare con me | Not About The Guitar |
| Verde | La seconda volta |
| Verde | Ricominciare |
| Verde | Niente è uguale |
| Verde | Torniamo insieme |
| Verde | I Knew You Were Magic |
| Verde | Blu |
| Verde | 42 |
| Terrorizzato | Fight Story |
Questo è il piano di esecuzione:
Nel piano di esecuzione, build è un'unione distribuita che
distribuisce le scansioni nella tabella Albums. Probe è un operatore di unione distribuita che distribuisce le scansioni sull'indice SongsBySingerAlbumSongNameDesc.
L'operatore Hash Join legge tutte le righe dal lato di build. Ogni riga di build viene
inserita in una tabella hash in base alle colonne della condizione a.SingerId =
s.SingerId AND a.AlbumId = s.AlbumId. Successivamente, l'operatore hash join legge tutte le righe del lato probe. Per ogni riga di probing, l'operatore di hash join cerca
corrispondenze nella tabella hash. Le corrispondenze risultanti vengono restituite dall'operatore
hash join.
Le corrispondenze risultanti nella tabella hash potrebbero anche essere filtrate in base a una condizione residua prima di essere restituite. Un esempio di dove compaiono le condizioni residue è nei join di non uguaglianza. I piani di esecuzione di Hash Join possono essere complessi a causa della gestione della memoria e delle varianti di join. L'algoritmo principale di hash join è adattato per gestire le varianti di inner join, semi join, anti join e outer join.
Unione
Un operatore merge join è un'implementazione basata sull'unione del join SQL. Entrambe le parti
del join producono righe ordinate in base alle colonne utilizzate nella condizione di join. Il
merge join utilizza entrambi i flussi di input contemporaneamente e restituisce le righe quando
la condizione di join è soddisfatta. Se gli input non sono originariamente ordinati come richiesto,
l'ottimizzatore aggiunge operatori Sort espliciti al piano.
Unione di tipo Merge non viene selezionata automaticamente dall'ottimizzatore. Per utilizzare questo operatore, imposta il metodo di unione su MERGE_JOIN nel suggerimento per la query, come mostrato nell'esempio seguente:
SELECT a.albumtitle,
s.songname
FROM albums AS a join@{join_method=merge_join} songs AS s
ON a.singerid = s.singerid
AND a.albumid = s.albumid;
Ecco i risultati:
| AlbumTitle | SongName |
|---|---|
| Verde | La seconda volta |
| Verde | Ricominciare |
| Verde | Niente è uguale |
| Verde | Torniamo insieme |
| Verde | I Knew You Were Magic |
| Verde | Blu |
| Verde | 42 |
| Terrorizzato | Fight Story |
| Niente a che fare con me | Not About The Guitar |
Questo è il piano di esecuzione:
In questo piano di esecuzione, l'unione di tipo merge viene distribuita in modo che l'unione venga eseguita
dove si trovano i dati. Ciò consente inoltre al merge join in questo esempio di
operare senza l'introduzione di operatori di ordinamento aggiuntivi, perché entrambe
le scansioni delle tabelle sono già ordinate per SingerId, AlbumId, che è la condizione
di join. In questo piano, la scansione sul lato sinistro della tabella Albums avanza
ogni volta che la coppia SingerId, AlbumId è relativamente inferiore alla coppia
SingerId_1, AlbumId_1 dell'indice di scansione SongsBySingerAlbumSongNameDesc sul lato destro.
Allo stesso modo, il lato destro avanza quando è inferiore al lato sinistro. Questa unione continua a cercare equivalenze in modo che
possano essere restituite le corrispondenze risultanti.
Considera un altro esempio di unione di tipo merge utilizzando la seguente query:
SELECT a.albumtitle,
s.songname
FROM albums AS a join@{join_method=merge_join} songs AS s
ON a.albumid = s.albumid;
Produce i seguenti risultati:
| AlbumTitle | SongName |
|---|---|
| Total Junk | La seconda volta |
| Total Junk | Ricominciare |
| Total Junk | Niente è uguale |
| Total Junk | Torniamo insieme |
| Total Junk | I Knew You Were Magic |
| Total Junk | Blu |
| Total Junk | 42 |
| Total Junk | Not About The Guitar |
| Verde | La seconda volta |
| Verde | Ricominciare |
| Verde | Niente è uguale |
| Verde | Torniamo insieme |
| Verde | I Knew You Were Magic |
| Verde | Blu |
| Verde | 42 |
| Verde | Not About The Guitar |
| Niente a che fare con me | La seconda volta |
| Niente a che fare con me | Ricominciare |
| Niente a che fare con me | Niente è uguale |
| Niente a che fare con me | Torniamo insieme |
| Niente a che fare con me | I Knew You Were Magic |
| Niente a che fare con me | Blu |
| Niente a che fare con me | 42 |
| Niente a che fare con me | Not About The Guitar |
| Riproduci | La seconda volta |
| Riproduci | Ricominciare |
| Riproduci | Niente è uguale |
| Riproduci | Torniamo insieme |
| Riproduci | I Knew You Were Magic |
| Riproduci | Blu |
| Riproduci | 42 |
| Riproduci | Not About The Guitar |
| Terrorizzato | Fight Story |
Questo è il piano di esecuzione:
Nel piano di esecuzione precedente, l'ottimizzatore delle query ha introdotto operatori Sort aggiuntivi per ottenere le proprietà necessarie per l'esecuzione del merge join. La condizione JOIN nella query di questo esempio riguarda solo AlbumId, che non è il modo in cui vengono archiviati i dati, quindi è necessario aggiungere un ordinamento. Il
motore di query supporta un algoritmo di unione distribuita, che consente l'ordinamento
in locale anziché a livello globale, il che distribuisce e parallelizza il costo della CPU.
Le corrispondenze risultanti potrebbero anche essere filtrate in base a una condizione residua prima di essere restituite. Un esempio di dove vengono visualizzate le condizioni residue è nei join di non uguaglianza. I piani di esecuzione di unione possono essere complessi a causa di ulteriori requisiti di ordinamento. L'algoritmo principale di unione è adattato per gestire le varianti di inner join, semi join, anti join e outer join.
Push broadcast hash join
Un operatore push broadcast hash join è un'implementazione distribuita basata su hash join di join SQL. L'operatore di join hash push broadcast legge le righe dal lato di input per costruire un batch di dati. Questo batch viene poi trasmesso a tutti i server contenenti dati laterali della mappa. Sui server di destinazione in cui viene ricevuto il batch di dati, viene creata un'unione hash utilizzando il batch come dati del lato di creazione e i dati locali vengono poi analizzati come lato di probing dell'unione hash.
Push broadcast hash join non viene selezionato automaticamente dall'ottimizzatore. Per utilizzare
questo operatore, imposta il metodo di unione su PUSH_BROADCAST_HASH_JOIN nel
suggerimento per la query, come mostrato nell'esempio seguente:
SELECT a.albumtitle,
s.songname
FROM albums AS a join@{join_method=push_broadcast_hash_join} songs AS s
ON a.singerid = s.singerid
AND a.albumid = s.albumid;
Ecco i risultati:
| AlbumTitle | SongName |
|---|---|
| Verde | La seconda volta |
| Verde | Ricominciare |
| Verde | Niente è uguale |
| Verde | Torniamo insieme |
| Verde | I Knew You Were Magic |
| Verde | Blu |
| Verde | 42 |
| Terrorizzato | Fight Story |
| Niente a che fare con me | Not About The Guitar |
Questo è il piano di esecuzione:
L'input dell'unione hash della trasmissione push è l'indice AlbumsByAlbumTitle.
L'input viene serializzato in un batch di dati. Il batch viene quindi inviato a tutte le suddivisioni locali dell'indice SongsBySingerAlbumSongNameDesc, dove viene deserializzato e creato in una tabella hash. La tabella hash utilizza quindi
i dati dell'indice locale come probe che restituisce le corrispondenze risultanti.
Le corrispondenze risultanti potrebbero anche essere filtrate in base a una condizione residua prima di essere restituite. Un esempio di dove vengono visualizzate le condizioni residue è nei join di non uguaglianza.
Applica esterno
Un operatore Outer Apply è simile a un operatore Cross Apply, solo che un operatore Outer Apply garantisce che ogni esecuzione sul lato mappa restituisca almeno una riga producendo una riga di valori NULL se necessario. (In altre parole, fornisce la semantica del left outer join.)
Unione ricorsiva
Un operatore recursive union esegue l'unione di due input, uno che rappresenta
un caso base e l'altro che rappresenta un caso recursive. Viene utilizzato nelle query grafiche con attraversamenti di percorsi quantificati. L'input di base viene elaborato per primo
e una sola volta. L'input ricorsivo viene elaborato finché la ricorsione
non termina. La ricorsione termina quando viene raggiunto il limite superiore, se specificato, o quando la ricorsione non produce nuovi risultati. Nell'esempio
seguente, la tabella Collaborations viene aggiunta allo schema e viene creato un grafico delle proprietà
denominato MusicGraph.
CREATE TABLE Collaborations (
SingerId INT64 NOT NULL,
FeaturingSingerId INT64 NOT NULL,
AlbumTitle STRING(MAX) NOT NULL,
) PRIMARY KEY(SingerId, FeaturingSingerId, AlbumTitle);
CREATE OR REPLACE PROPERTY GRAPH MusicGraph
NODE TABLES(
Singers
KEY(SingerId)
LABEL Singers PROPERTIES(
BirthDate,
FirstName,
LastName,
SingerId,
SingerInfo)
)
EDGE TABLES(
Collaborations AS CollabWith
KEY(SingerId, FeaturingSingerId, AlbumTitle)
SOURCE KEY(SingerId) REFERENCES Singers(SingerId)
DESTINATION KEY(FeaturingSingerId) REFERENCES Singers(SingerId)
LABEL CollabWith PROPERTIES(
AlbumTitle,
FeaturingSingerId,
SingerId),
);
La seguente query del grafico trova i cantanti che hanno collaborato con un determinato cantante o con i suoi collaboratori.
GRAPH MusicGraph
MATCH (singer:Singers {singerId:42})-[c:CollabWith]->{1,2}(featured:Singers)
RETURN singer.SingerId AS singer, featured.SingerId AS featured
L'operatore di unione ricorsiva filtra la tabella Singers per trovare il cantante
con il SingerId specificato. Questo è l'input di base per l'unione ricorsiva. L'input ricorsivo per l'unione ricorsiva comprende un cross-apply distribuito o un altro operatore di join per altre query che uniscono ripetutamente la tabella Collaborations con i risultati dell'iterazione precedente del join. Le righe del modulo di input di base costituiscono la prima iterazione.
A ogni iterazione, l'output viene archiviato dalla scansione
dello spool ricorsivo. Le righe della scansione spool ricorsiva vengono unite alla tabella Collaborations in spoolscan.featuredSingerId = Collaborations.SingerId. La ricorsione
termina al completamento di due iterazioni, poiché questo è il limite
superiore specificato nella query.
Operatori n-ari
Un operatore N-ario è un operatore che ha più di due elementi secondari relazionali. I seguenti operatori sono operatori N-ari:
Union all
Un operatore Union All combina tutti i set di righe dei relativi elementi secondari senza rimuovere i duplicati. Gli operatori Union All ricevono l'input dagli operatori Union Input distribuiti su più server. L'operatore Union All richiede che gli input abbiano lo stesso schema, ovvero lo stesso insieme di tipi di dati per ogni colonna.
Ad esempio, utilizzando questa query:
SELECT 1 a,
2 b
UNION ALL
SELECT 3 a,
4 b
UNION ALL
SELECT 5 a,
6 b;
Il tipo di riga per i figli è costituito da due numeri interi.
Ecco i risultati:
| a | b |
|---|---|
| 1 | 2 |
| 3 | 4 |
| 5 | 6 |
Questo è il piano di esecuzione:
L'operatore Union All combina le righe di input e, in questo esempio, invia i risultati a un operatore Serialize Result.
Una query come la seguente andrebbe a buon fine, perché per ogni colonna viene utilizzato lo stesso insieme di tipi di dati, anche se i figli utilizzano variabili diverse per i nomi delle colonne:
SELECT 1 a,
2 b
UNION ALL
SELECT 3 c,
4 e;
Una query come la seguente non andrebbe a buon fine perché i figli utilizzano tipi di dati diversi per le colonne:
SELECT 1 a,
2 b
UNION ALL
SELECT 3 a,
'This is a string' b;
Sottoquery scalari
Una sottoquery scalare è una sottoespressione SQL che fa parte di un'espressione scalare. Spanner tenta di rimuovere le sottoquery scalari laddove possibile. In alcuni scenari, tuttavia, i piani possono contenere esplicitamente sottoquery scalari.
Ad esempio, utilizzando questa query:
SELECT firstname,
IF(firstname = 'Alice', (SELECT Count(*)
FROM songs
WHERE duration > 300), 0)
FROM singers;
Questa è la sottoespressione SQL:
SELECT Count(*)
FROM songs
WHERE duration > 300;
Ecco i risultati (della query completa):
| Nome | |
|---|---|
| Alice | 1 |
| Catalina | 0 |
| Davide | 0 |
| Lea | 0 |
| Marc | 0 |
Questo è il piano di esecuzione:
Il piano di esecuzione contiene una sottoquery scalare, mostrata come Scalar Subquery, su un operatore aggregate.
A volte Spanner converte le sottoquery scalari in un altro operatore, ad esempio un join o un cross apply, per migliorare potenzialmente le prestazioni.
Ad esempio, utilizzando questa query:
SELECT *
FROM songs
WHERE duration = (SELECT Max(duration)
FROM songs);
Questa è la sottoespressione SQL:
SELECT MAX(Duration)
FROM Songs;
Ecco i risultati (della query completa):
| SingerId | AlbumId | TrackId | SongName | Durata | SongGenre |
|---|---|---|---|---|---|
| 2 | 1 | 6 | Niente è uguale | 303 | BLUES |
Questo è il piano di esecuzione:
Il piano di esecuzione non contiene una sottoquery scalare perché Spanner ha convertito la sottoquery scalare in un'applicazione incrociata.
Sottoquery di array
Una sottoquery array è simile a una sottoquery scalare, tranne per il fatto che la sottoquery può utilizzare più di una riga di input. Le righe utilizzate vengono convertite in un unico array di output scalare contenente un elemento per riga di input utilizzata.
Ad esempio, utilizzando questa query:
SELECT a.albumid,
array
(
select concertdate
FROM concerts
WHERE concerts.singerid = a.singerid)
FROM albums AS a;
Questa è la sottoquery:
SELECT concertdate
FROM concerts
WHERE concerts.singerid = a.singerid;
I risultati della sottoquery per ogni AlbumId vengono convertiti in un array di
righe ConcertDate rispetto a quel AlbumId. Il piano di esecuzione contiene una sottoquery
di array, mostrata come Array Subquery, sopra un operatore di unione distribuita:
Operatori distribuiti
Gli operatori descritti in precedenza in questa pagina vengono eseguiti entro i limiti di un singolo computer. Gli operatori distribuiti vengono eseguiti su più server.
I seguenti operatori sono operatori distribuiti:
- Unione distribuita
- Unione di unione distribuita
- Applicazione incrociata distribuita
- Distributed outer apply
- Applicare le mutazioni
L'operatore Distributed Union è l'operatore primitivo da cui derivano Distributed Cross Apply e Distributed Outer Apply.
Gli operatori distribuiti vengono visualizzati nei piani di esecuzione con una variante di unione distribuita sopra una o più varianti di unione distribuita locale. Una variante di un'operazione Union distribuita esegue la distribuzione remota dei sottopiani. Una variante dell'unione distribuita locale si trova in cima a ciascuna delle scansioni eseguite per la query, come mostrato in questo piano di esecuzione:
Le varianti dell'operazione Union distribuita locale garantiscono l'esecuzione stabile delle query quando si verificano riavvii perconfini della suddivisionee che cambiano dinamicamente.
Quando possibile, una variante di unione distribuita ha un predicato di suddivisione che comporta l'eliminazione delle suddivisioni, il che significa che i server remoti eseguono i sottopiani solo sulle suddivisioni che soddisfano il predicato. Ciò migliora sia la latenza che il rendimento complessivo delle query.
Distributed union
Un operatore distributed union divide concettualmente una o più tabelle in più split, valuta in remoto una sottoquery in modo indipendente su ogni split e poi unisce tutti i risultati.
Ad esempio, utilizzando questa query:
SELECT s.songname,
s.songgenre
FROM songs AS s
WHERE s.singerid = 2
AND s.songgenre = 'ROCK';
Ecco i risultati:
| SongName | SongGenre |
|---|---|
| Ricominciare | ROCK |
| La seconda volta | ROCK |
| Fight Story | ROCK |
Questo è il piano di esecuzione:
L'operatore di unione distribuita invia i piani secondari ai server remoti, che eseguono una
scansione della tabella tra le suddivisioni che soddisfano il predicato WHERE
s.SingerId = 2 AND s.SongGenre = 'ROCK' della query. Un operatore serialize
result calcola i valori SongName e SongGenre
dalle righe restituite dalle scansioni delle tabelle. L'operatore di unione distribuita
restituisce quindi i risultati combinati dei server remoti come risultati della query SQL.
Unione di unione distribuita
L'operatore distributed merge union distribuisce una query su più server remoti. Quindi combina i risultati della query per produrre un risultato ordinato, noto come ordinamento di unione distribuito.
Un'unione di merge distribuita esegue i seguenti passaggi:
Il server radice invia una sottoquery a ogni server remoto che ospita una divisione dei dati sottoposti a query. La sottoquery include istruzioni che i risultati vengono ordinati in un ordine specifico.
Ogni server remoto esegue la sottoquery sulla propria suddivisione, quindi invia i risultati nell'ordine richiesto.
Il server radice unisce la sottoquery ordinata per produrre un risultato completamente ordinato.
L'unione distribuita è attiva per impostazione predefinita per Spanner versione 3 e successive.
Distributed Cross Apply
Un operatore distributed cross apply (DCA) estende l'operatore cross apply eseguendo l'operazione su più server. I gruppi laterali di input DCA elaborano batch di righe (a differenza di un normale operatore Cross Apply, che agisce su una sola riga di input alla volta). Il lato mappa DCA è un insieme di operatori Cross Apply che vengono eseguiti su server remoti.
Ad esempio, utilizzando questa query:
SELECT albumtitle
FROM songs
JOIN albums
ON albums.albumid = songs.albumid;
I risultati sono nel formato:
| AlbumTitle |
|---|
| Verde |
| Niente a che fare con me |
| Riproduci |
| Total Junk |
| Verde |
Questo è il piano di esecuzione:
L'input DCA contiene una scansione dell'indice
SongsBySingerAlbumSongNameDesc che raggruppa le righe di AlbumId. Il lato
della mappa per questo operatore di applicazione incrociata è una scansione dell'indice
AlbumsByAlbumTitle, soggetta al predicato di AlbumId nella riga di input
che corrisponde alla chiave AlbumId nell'indice AlbumsByAlbumTitle. Il mapping
restituisce SongName per i valori SingerId nelle righe di input batch.
Per riassumere la procedura DCA per questo esempio, l'input del DCA sono le righe in batch
della tabella Albums e l'output del DCA è l'applicazione di queste
righe alla mappa della scansione dell'indice.
Applicazione esterna distribuita
Un operatore Distributed Outer Apply estende l'operatore Outer Apply eseguendo l'operazione su più server, in modo simile a come un operatore Distributed Cross Apply estende un operatore Cross Apply.
Ad esempio, utilizzando questa query:
SELECT lastname,
concertdate
FROM singers LEFT OUTER join@{JOIN_TYPE=APPLY_JOIN} concerts
ON singers.singerid=concerts.singerid;
I risultati sono nel formato:
| Cognome | ConcertDate |
|---|---|
| Trentor | 2014-02-18 |
| Smith | 2011-09-03 |
| Smith | 2010-06-06 |
| Lomond | 2005-04-30 |
| Martin | 2015-11-04 |
| Richards |
Questo è il piano di esecuzione:
Applica mutazioni
Un operatore apply mutations applica le mutazioni di un'istruzione DML (Data Manipulation Statement) alla tabella. È l'operatore principale in un piano di query per un'istruzione DML.
Ad esempio, utilizzando questa query:
DELETE FROM singers
WHERE firstname = 'Alice';
Ecco i risultati:
4 rows deleted This statement deleted 4 rows and did not return any rows.
Questo è il piano di esecuzione:
Informazioni aggiuntive
Questa sezione descrive gli elementi che non sono operatori autonomi, ma che eseguono attività per supportare uno o più degli operatori elencati in precedenza. Gli elementi descritti qui sono tecnicamente operatori, ma non sono operatori separati nel piano di query.
Costruttore di struct
Un costruttore di struct crea uno struct, ovvero una raccolta di campi. In genere crea una struct per le righe risultanti da un'operazione di calcolo. Un costruttore di struct non è un operatore autonomo. Viene invece visualizzato negli operatori compute struct o serialize result.
Per un'operazione di struttura di calcolo, il costruttore di strutture crea una struttura in modo che le colonne per le righe calcolate possano utilizzare un unico riferimento di variabile alla struttura.
Per un'operazione di serializzazione dei risultati, il costruttore di struct crea uno struct per serializzare i risultati.
Ad esempio, utilizzando questa query:
SELECT IF(TRUE, struct(1 AS A, 1 AS B), struct(2 AS A , 2 AS B)).A;
Ecco i risultati:
| A |
|---|
| 1 |
Questo è il piano di esecuzione:
Nel piano di esecuzione, i costruttori di struct vengono visualizzati all'interno di un operatore di risultato di serializzazione.