Client
Spanner supporta le query SQL. Ecco una query di esempio:
SELECT s.SingerId, s.FirstName, s.LastName, s.SingerInfo
FROM Singers AS s
WHERE s.FirstName = @firstName;
Il costrutto @firstName è un riferimento a un parametro di query. Puoi utilizzare un
parametro di query ovunque sia possibile utilizzare un valore letterale. L'utilizzo di parametri nelle API programmatiche è vivamente consigliato. L'utilizzo dei parametri di ricerca consente di evitare
attacchi di SQL injection e le query risultanti hanno maggiori probabilità
di trarre vantaggio da varie cache lato server. Per saperne di più, consulta
Memorizzazione nella cache.
I parametri di query devono essere associati a un valore quando la query viene eseguita. Ad esempio:
Statement statement =
Statement.newBuilder("SELECT s.SingerId...").bind("firstName").to("Jimi").build();
try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
while (resultSet.next()) {
...
}
}
Quando Spanner riceve una chiamata API, analizza la query e i parametri associati per determinare quale nodo server Spanner deve elaborare la query. Il server restituisce un flusso di righe di risultati che vengono
utilizzate dalle chiamate a ResultSet.next().
Esecuzione delle query
L'esecuzione della query inizia con l'arrivo di una richiesta "execute query" su un server Spanner. Il server esegue i seguenti passaggi:
- Convalidare la richiesta
- Analizza il testo della query
- Genera un'algebra di query iniziale
- Genera un'algebra delle query ottimizzata
- Generare un piano di query eseguibile
- Esegui il piano (controlla le autorizzazioni, leggi i dati, codifica i risultati e così via).
Analisi
Il parser SQL analizza il testo della query e lo converte in un albero della sintassi
astratta. Estrae la struttura di base della query (SELECT …
FROM … WHERE …) ed esegue controlli sintattici.
Algebra
Il sistema di tipi di Spanner può rappresentare scalari, array, strutture e così via. L'algebra delle query definisce gli operatori per le scansioni delle tabelle, il filtraggio, l'ordinamento/il raggruppamento, tutti i tipi di join, l'aggregazione e molto altro ancora. L'algebra della query iniziale viene creata dall'output del parser. I riferimenti al nome del campo nell'albero di analisi vengono risolti utilizzando lo schema del database. Questo codice controlla anche gli errori semantici (ad es. numero errato di parametri, mancata corrispondenza dei tipi e così via).
Il passaggio successivo ("ottimizzazione delle query") prende l'algebra iniziale e genera un'algebra più ottimale. Potrebbe essere più semplice, più efficiente o semplicemente più adatto alle funzionalità del motore di esecuzione. Ad esempio, l'algebra iniziale potrebbe specificare solo un "join", mentre l'algebra ottimizzata specifica un "hash join".
Esecuzione
Il piano di query eseguibile finale viene creato dall'algebra riscritta. In sostanza, il piano eseguibile è un grafo aciclico orientato di "iteratori". Ogni iteratore espone una sequenza di valori. Gli iteratori possono utilizzare gli input per produrre output (ad es. l'iteratore di ordinamento). Le query che coinvolgono una singola suddivisione possono essere eseguite da un singolo server (quello che contiene i dati). Il server esegue la scansione degli intervalli di varie tabelle, esegue i join, esegue l'aggregazione e tutte le altre operazioni definite dall'algebra delle query.
Le query che prevedono più suddivisioni verranno suddivise in più parti. Alcune parti della query continueranno a essere eseguite sul server principale (root). Le altre query secondarie parziali vengono trasferite ai nodi foglia (quelli che possiedono le suddivisioni in lettura). Questo trasferimento può essere applicato in modo ricorsivo per query complesse, generando un albero di esecuzioni del server. Tutti i server concordano un timestamp in modo che i risultati della query siano uno snapshot coerente dei dati. Ogni server foglia restituisce un flusso di risultati parziali. Per le query che coinvolgono l'aggregazione, questi potrebbero essere risultati parzialmente aggregati. Il server radice della query elabora i risultati dei server foglia ed esegue il resto del piano di query. Per maggiori informazioni, consulta Piani di esecuzione delle query.
Quando una query prevede più suddivisioni, Spanner può eseguirla in parallelo tra le suddivisioni. Il grado di parallelismo dipende dall'intervallo di dati analizzati dalla query, dal piano di esecuzione della query e dalla distribuzione dei dati tra le suddivisioni. Spanner imposta automaticamente il grado massimo di parallelismo per una query in base alle dimensioni dell'istanza e alla configurazione dell'istanza (regionale o multiregionale) per ottenere prestazioni ottimali delle query ed evitare di sovraccaricare la CPU.
Memorizzazione nella cache
Molti degli artefatti dell'elaborazione delle query vengono memorizzati automaticamente nella cache e riutilizzati
per le query successive. Sono inclusi algebre di query, piani di query eseguibili e così via. La memorizzazione nella cache si basa sul testo della query, sui nomi e sui tipi di parametri associati. Per questo motivo, l'utilizzo di parametri vincolati (come @firstName nell'esempio precedente) è preferibile all'utilizzo di valori letterali nel testo della query. Il
primo può essere memorizzato nella cache una sola volta e riutilizzato indipendentemente dal valore limite effettivo. Per ulteriori dettagli, consulta
Ottimizzazione delle prestazioni delle query Spanner.
Gestione degli errori
I metodi executeQuery (o executeStreamingSql) e streamingRead restituiscono
un flusso di messaggi PartialResultSet. Per efficienza, un singolo valore di riga o
colonna potrebbe essere suddiviso in più messaggi PartialResultSet,
soprattutto per grandi quantità di dati.
Questo flusso può essere interrotto da errori di rete temporanei, passaggi divisi o riavvii del server. I trasferimenti divisi potrebbero verificarsi durante il bilanciamento del carico e i riavvii del server potrebbero verificarsi durante gli upgrade.
Per gestire queste interruzioni, Spanner include stringhe opache
resume_token in alcuni messaggi PartialResultSet.
Punti chiave relativi a resume_token:
- Non tutti gli elementi
PartialResultSetcontengono un elementoresume_token. - Un
resume_tokenviene in genere incluso solo alla fine di una riga completa, per indicare un punto di ripresa sicuro. PartialResultSetcon unchunked_value(per valori di grandi dimensioni suddivisi in più messaggi) non avrà unresume_tokenfinché non vengono inviati l'intero valore e la riga.- Per riprendere uno stream interrotto, invia una nuova richiesta con il
last received
resume_tokennon vuoto.
Le librerie client di Spanner gestiscono automaticamente questo buffering
e il recupero. Assemblano righe complete da PartialResultSet messaggi
e monitorano l'ultimo resume_token. Se la connessione si interrompe, la libreria utilizza
l'ultimo token valido per riavviare lo stream, scartando tutti i dati parziali
ricevuti dopo il token. Questo processo ti garantisce un flusso continuo
di righe complete e senza duplicati, anche in caso di errori temporanei.