Esta página descreve detalhes sobre os operadores usados nos planos de execução de consultas do Spanner. Para saber como obter um plano de execução para uma consulta específica através da consola Google Cloud , consulte o artigo Compreender como o Spanner executa consultas.
As consultas e os planos de execução nesta página baseiam-se no seguinte esquema da base de dados:
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);
Pode usar as seguintes declarações da linguagem de manipulação de dados (DML) para adicionar dados a estas tabelas:
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");
Operadores de folhas
Um operador folha é um operador que não tem filhos. Os tipos de operadores de folhas são:
Desaninhamento de matriz
Um operador array unnest transforma uma matriz de entrada em linhas de elementos. Cada linha resultante contém até duas colunas: o valor real da matriz e, opcionalmente, a posição baseada em zero na matriz.
Por exemplo, usando esta consulta:
SELECT a, b FROM UNNEST([1,2,3]) a WITH OFFSET b;
A consulta reduz a matriz [1,2,3]
na coluna a
e mostra a posição da matriz na coluna b
.
Estes são os resultados:
a | b |
---|---|
1 | 0 |
2 | 1 |
3 | 2 |
Este é o plano de execução:
Gere a relação
Um operador generate relation devolve zero ou mais linhas.
Relação de unidades
A relação de unidade devolve uma linha. É um caso especial do operador generate relation.
Por exemplo, usando esta consulta:
SELECT 1 + 2 AS Result;
O resultado é:
Resultado |
---|
3 |
Este é o plano de execução:
Relação vazia
A relação vazia não devolve linhas. É um caso especial do operador generate relation.
Por exemplo, usando esta consulta:
SELECT *
FROM albums
LIMIT 0
O resultado é:
Nenhum resultado
Este é o plano de execução:
A digitalizar
Um operador scan devolve linhas ao analisar uma origem de linhas. Estes são os tipos de operadores de procura:
- Leitura de mesa: a leitura ocorre numa mesa.
- Análise de índice: a análise ocorre num índice.
- Análise em lote: a análise ocorre em tabelas intermédias criadas por outros operadores relacionais (por exemplo, uma tabela criada por uma aplicação cruzada distribuída).
Sempre que possível, o Spanner aplica predicados em chaves como parte de uma análise. As análises são executadas de forma mais eficiente quando são aplicados predicados, porque a análise não precisa de ler a tabela ou o índice completo. Os predicados aparecem no plano de execução no formato KeyPredicate: column=value
.
No pior caso, uma consulta pode ter de procurar todas as linhas numa tabela. Esta situação leva a uma análise completa e aparece no plano de execução como full scan:
true
.
Por exemplo, usando esta consulta:
SELECT s.lastname
FROM singers@{FORCE_INDEX=SingersByFirstLastName} as s
WHERE s.firstname = 'Catalina';
Estes são os resultados:
LastName |
---|
Smith |
Este é o plano de execução:
No plano de execução, o operador distributed union de nível superior envia subplanos para servidores remotos. Cada subplano tem um operador serialize
result e um operador de análise de índice. O predicado Key Predicate: FirstName = 'Catalina'
restringe a análise às linhas no índiceSingersByFirstLastname
que têm FirstName
igual a Catalina
. O resultado da análise de índice é devolvido ao operador de resultado de serialização.
Operadores unários
Um operador unário é um operador que tem um único filho relacional.
Os seguintes operadores são operadores unários:
- Agregado
- Aplique mutações
- Criar lote
- Computação
- Compute struct
- DataBlockToRowAdapter
- Filtro
- Filtrar análise
- Limite
- Local split union
- Random Id Assign
- RowToDataBlockAdapter
- Serializar resultado
- Ordenar
- TVF
- Entrada de união
Agregar
Um operador aggregate implementa declarações SQL GROUP BY
e funções aggregate (como COUNT
). A entrada para um operador aggregate é logicamente particionada em grupos organizados em colunas de chaves (ou num único grupo se GROUP BY
não estiver presente). Para cada grupo, são calculados zero ou mais agregados.
Por exemplo, usando esta consulta:
SELECT s.singerid,
Avg(s.duration) AS average,
Count(*) AS count
FROM songs AS s
GROUP BY singerid;
A consulta agrupa por SingerId
e executa uma agregação AVG
e uma agregação COUNT
.
Estes são os resultados:
SingerId | média | contagem |
---|---|---|
3 | 278 | 1 |
2 | 225,875 | 8 |
Este é o plano de execução:
Os operadores de agregação podem ser baseados em streams ou baseados em hash. O plano de execução anterior mostra um agregado baseado em streams. Os agregados baseados em streams leem a entrada já pré-ordenada (se GROUP BY
estiver presente) e calculam o grupo sem bloqueio. Os dados agregados baseados em hash criam tabelas de hash para manter os dados agregados incrementais de várias linhas de entrada em simultâneo. Os agregados baseados em streams são mais rápidos e usam menos memória do que os agregados baseados em hash, mas requerem que a entrada seja ordenada (por colunas-chave ou índices secundários).
Para cenários distribuídos, um operador agregado pode ser separado num par local-global. Cada servidor remoto executa a agregação local nas respetivas linhas de entrada e, em seguida, devolve os resultados ao servidor raiz. O servidor de raiz realiza a agregação global.
Aplique mutações
Um operador apply mutations aplica as mutações de uma declaração de manipulação de dados (DML) à tabela. É o operador principal num plano de consulta para uma declaração DML.
Por exemplo, usando esta consulta:
DELETE FROM singers
WHERE firstname = 'Alice';
Estes são os resultados:
4 rows deleted This statement deleted 4 rows and did not return any rows.
Este é o plano de execução:
Criar lote
Um operador create batch agrupa as linhas de entrada numa sequência. Normalmente, uma operação de criação em lote ocorre como parte de uma operação de aplicação cruzada distribuída. As linhas de entrada podem ser reordenadas durante o processamento em lote. O número de linhas de entrada que são processadas em lote em cada execução do operador de lote varia.
Consulte o operador distributed cross apply para ver um exemplo de um operador de criação de lotes num plano de execução.
Computação
Um operador de cálculo produz resultados lendo as respetivas linhas de entrada e adicionando uma ou mais colunas adicionais que são calculadas através de expressões escalares. Consulte o operador union all para ver um exemplo de um operador de cálculo num plano de execução.
Estrutura de computação
Um operador compute struct cria uma variável para uma estrutura que contém campos para cada uma das colunas de entrada.
Por exemplo, usando esta consulta:
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;
Estes são os resultados:
FirstName | Não especificado |
---|---|
Alice | [["Not About The Guitar","BLUES"]] |
Este é o plano de execução:
No plano de execução, o operador de subconsulta de matriz recebe entrada de um operador de união distribuída, que recebe entrada de um operador de estrutura de cálculo. O operador compute struct cria uma estrutura a partir das colunas SongName
e SongGenre
na tabela Songs
.
DataBlockToRowAdapter
Um operador DataBlockToRowAdapter
é inserido automaticamente pelo otimizador de consultas do Spanner entre um par de operadores que funcionam com métodos de execução diferentes. A sua entrada é um operador que usa o método de execução orientado por lotes e a sua saída é introduzida num operador que executa o método de execução orientado por linhas. Para mais informações, consulte o artigo
Otimize a execução de consultas.
Filtro
Um operador de filtro lê todas as linhas da respetiva entrada, aplica um predicado escalar em cada linha e, em seguida, devolve apenas as linhas que satisfazem o predicado.
Por exemplo, usando esta consulta:
SELECT s.lastname
FROM (SELECT s.lastname
FROM singers AS s
LIMIT 3) s
WHERE s.lastname LIKE 'Rich%';
Estes são os resultados:
LastName |
---|
Richards |
Este é o plano de execução:
O predicado para cantores cujo apelido começa por Rich
é implementado como um filtro. A entrada do filtro é a saída de uma análise de índice e a saída do filtro são linhas em que LastName
começa por Rich
.
Para o desempenho, sempre que um filtro estiver posicionado diretamente acima de uma análise,
o filtro afeta a forma como os dados são lidos. Por exemplo, considere uma tabela com a chave k
.
Um filtro com predicado k = 5
diretamente sobre uma análise da tabela procura linhas que correspondam a k = 5
, sem ler a entrada completa. Isto resulta numa execução mais eficiente da consulta. No exemplo anterior, o operador de filtro lê apenas as linhas que satisfazem o predicado WHERE s.LastName LIKE 'Rich%'
.
Digitalização de filtros
Um operador de análise de filtro está sempre acima de uma análise de tabela ou de índice. Funciona com a análise para reduzir o número de linhas lidas da base de dados e a análise resultante é normalmente mais rápida do que com um filtro. O Spanner aplica a análise de filtros em determinadas condições:
- Condição de pesquisa: a condição de pesquisa aplica-se se o Spanner conseguir determinar uma linha específica para aceder na tabela. Em geral, isto acontece quando o filtro está num prefixo da chave principal. Por exemplo, se a chave primária consistir em
Col1
eCol2
, então uma cláusulaWHERE
que inclua valores explícitos paraCol1
ouCol1
eCol2
é pesquisável. Nesse caso, o Spanner lê dados apenas dentro do intervalo de chaves. - Condição residual: qualquer outra condição em que o Spanner possa avaliar a análise para limitar a quantidade de dados lidos.
Por exemplo, usando esta consulta:
SELECT lastname
FROM singers
WHERE singerid = 1
Estes são os resultados:
LastName |
---|
Richards |
Este é o plano de execução:
Limite
Um operador limit restringe o número de linhas devolvidas. Um parâmetro OFFSET
opcional especifica a linha inicial a devolver. Para cenários distribuídos, um operador de limite pode ser separado num par local-global. Cada servidor remoto aplica o limite local às respetivas linhas de saída e, em seguida, devolve os resultados ao servidor raiz. O servidor raiz agrega as linhas enviadas pelos servidores remotos e, em seguida, aplica o limite global.
Por exemplo, usando esta consulta:
SELECT s.songname
FROM songs AS s
LIMIT 3;
Estes são os resultados:
SongName |
---|
Not About The Guitar |
A segunda vez |
Recomeçar |
Este é o plano de execução:
O limite local é o limite para cada servidor remoto. O servidor raiz agrega as linhas dos servidores remotos e, em seguida, aplica o limite global.
Atribuição de ID aleatório
Um operador de atribuição de ID aleatório produz resultados lendo as respetivas linhas de entrada e adicionando um número aleatório a cada linha. Funciona com um operador Filter
ou Sort
para alcançar métodos de amostragem. Os métodos de amostragem suportados são Bernoulli e Reservoir.
Por exemplo, a seguinte consulta usa a amostragem de Bernoulli com uma taxa de amostragem de 10 por cento.
SELECT s.songname
FROM songs AS s TABLESAMPLE bernoulli (10 PERCENT);
Estes são os resultados:
SongName |
---|
Recomeçar |
Nothing Is The Same |
Tenha em atenção que, uma vez que o resultado é uma amostra, pode variar cada vez que a consulta é executada, mesmo que seja a mesma.
Este é o plano de execução:
Neste plano de execução, o operador Random Id Assign
recebe a respetiva entrada de um operador de união distribuída, que recebe a respetiva entrada de uma análise de índice. O operador devolve as linhas com IDs aleatórios e, em seguida, o operador Filter
aplica um predicado escalar aos IDs aleatórios e devolve aproximadamente 10% das linhas.
O exemplo seguinte usa a amostragem de reservatório com uma taxa de amostragem de 2 linhas.
SELECT s.songname
FROM songs AS s TABLESAMPLE reservoir (2 rows);
Estes são os resultados:
SongName |
---|
I Knew You Were Magic |
A segunda vez |
Tenha em atenção que, uma vez que o resultado é uma amostra, pode variar cada vez que a consulta é executada, mesmo que seja a mesma.
Este é o plano de execução:
Neste plano de execução, o operador Random Id Assign
recebe a respetiva entrada de um operador de união distribuída, que recebe a respetiva entrada de uma análise de índice. O operador devolve as linhas com IDs aleatórios e, em seguida, o operador Sort
aplica a ordem de ordenação aos IDs aleatórios e aplica LIMIT
com 2 linhas.
Local split union
Um operador de união de divisão local encontra divisões de tabelas armazenadas no servidor local, executa uma subconsulta em cada divisão e, em seguida, cria uma união que combina todos os resultados.
Uma união de divisão local aparece em planos de execução que analisam uma tabela de posicionamento. Os posicionamentos podem aumentar o número de divisões numa tabela, tornando mais eficiente a análise de divisões em lotes com base nas respetivas localizações de armazenamento físico.
Por exemplo, suponhamos que a tabela Singers
usa uma chave de posicionamento para particionar os dados de cantores:
CREATE TABLE Singers (
SingerId INT64 NOT NULL,
SingerName STRING(MAX) NOT NULL,
...
Location STRING(MAX) NOT NULL PLACEMENT KEY
) PRIMARY KEY (SingerId);
Agora, considere esta consulta:
SELECT BirthDate FROM Singers;
Este é o plano de execução:
A união distribuída envia uma subconsulta a cada lote de divisões armazenadas fisicamente em conjunto no mesmo servidor. Em cada servidor, a união de divisão
local encontra divisões que armazenam dados Singers
, executa a subconsulta em cada divisão e devolve os resultados combinados. Desta forma, a união distribuída e a união dividida local funcionam em conjunto para analisar de forma eficiente a tabela Singers
.
Sem uma união dividida local, a união distribuída enviaria um RPC por divisão, em vez de por lote de divisão, o que resultaria em viagens de ida e volta de RPC redundantes quando existe mais do que uma divisão por lote.
RowToDataBlockAdapter
Um operador RowToDataBlockAdapter
é inserido automaticamente pelo otimizador de consultas do Spanner entre um par de operadores que funcionam com métodos de execução diferentes. A sua entrada é um operador que usa o método de execução orientado por linhas e a sua saída é introduzida num operador que executa o método de execução orientado por lotes. Para mais informações, consulte o artigo
Otimize a execução de consultas.
Serializar resultado
Um operador serialize result é um caso especial do operador compute struct que serializa cada linha do resultado final da consulta para devolver ao cliente.
Por exemplo, usando esta consulta:
SELECT array
(
select as struct so.songname,
so.songgenre
FROM songs AS so
WHERE so.singerid = s.singerid)
FROM singers AS s;
A consulta pede uma matriz de SongName
e SongGenre
com base em SingerId
.
Estes são os resultados:
Não especificado |
---|
[] |
[[Let's Get Back Together, COUNTRY], [Starting Again, ROCK]] |
[[Not About The Guitar, BLUES]] |
[] |
[] |
Este é o plano de execução:
O operador de resultado de serialização cria um resultado que contém, para cada linha da tabela Singers
, uma matriz de pares SongName
e SongGenre
para as músicas do cantor.
Ordenar
Um operador sort lê as linhas de entrada, ordena-as por colunas e, em seguida, devolve os resultados ordenados.
Por exemplo, usando esta consulta:
SELECT s.songgenre
FROM songs AS s
ORDER BY songgenre;
Estes são os resultados:
SongGenre |
---|
BLUES |
BLUES |
BLUES |
BLUES |
CLÁSSICA |
PAÍS |
ROCK |
ROCK |
ROCK |
Este é o plano de execução:
Neste plano de execução, o operador de ordenação recebe as respetivas linhas de entrada de um operador de união distribuída, ordena as linhas de entrada e devolve as linhas ordenadas a um operador de serialização de resultados.
Para restringir o número de linhas devolvidas, um operador de ordenação pode ter opcionalmente parâmetros LIMIT
e OFFSET
. Para cenários distribuídos, um operador de ordenação com um operador LIMIT
ou OFFSET
é separado num par local-global. Cada servidor remoto aplica a ordem de ordenação e o limite ou o desvio local para as respetivas linhas de entrada e, em seguida, devolve os resultados ao servidor raiz. O servidor raiz agrega as linhas enviadas pelos servidores remotos, ordena-as e, em seguida, aplica o limite/desvio global.
Por exemplo, usando esta consulta:
SELECT s.songgenre
FROM songs AS s
ORDER BY songgenre
LIMIT 3;
Estes são os resultados:
SongGenre |
---|
BLUES |
BLUES |
BLUES |
Este é o plano de execução:
O plano de execução mostra o limite local para os servidores remotos e o limite global para o servidor raiz.
TVF
Um operador de função de valor de tabela produz resultados lendo as respetivas linhas de entrada e aplicando a função especificada. A função pode implementar o mapeamento e devolver o mesmo número de linhas que a entrada. Também pode ser um gerador que devolve mais linhas ou um filtro que devolve menos linhas.
Por exemplo, usando esta consulta:
SELECT genre,
songname
FROM ml.predict(model genreclassifier, TABLE songs)
Estes são os resultados:
Género | SongName |
---|---|
País | Not About The Guitar |
Rock | A segunda vez |
Pop | Recomeçar |
Pop | Nothing Is The Same |
País | Vamos voltar a juntar-nos |
Pop | I Knew You Were Magic |
Eletrónica | Azul |
Rock | 42 |
Rock | História de luta |
Este é o plano de execução:
Entrada da união
Um operador union input devolve resultados a um operador union all. Consulte o operador union all para ver um exemplo de um operador de entrada de união num plano de execução.
Operadores binários
Um operador binário é um operador que tem dois filhos relacionais. Os seguintes operadores são operadores binários:
- Aplicação cruzada
- Junção de hash
- Junção de intercalação
- Push broadcast hash join
- Aplicação exterior
- União recursiva
Aplicação cruzada
Um operador cross apply executa uma consulta de tabela em cada linha obtida por uma consulta de outra tabela e devolve a união de todas as execuções de consultas de tabelas. Os operadores de aplicação cruzada e aplicação externa executam o processamento orientado por linhas, ao contrário dos operadores que executam o processamento baseado em conjuntos, como a junção hash . O operador de aplicação cruzada tem duas entradas: input e map. O operador cross apply aplica cada linha do lado de entrada ao lado de mapeamento. O resultado da aplicação cruzada tem colunas dos lados de entrada e de mapeamento.
Por exemplo, usando esta consulta:
SELECT si.firstname,
(SELECT so.songname
FROM songs AS so
WHERE so.singerid = si.singerid
LIMIT 1)
FROM singers AS si;
A consulta pede o nome próprio de cada cantor, juntamente com o nome de apenas uma das músicas do cantor.
Estes são os resultados:
FirstName | Não especificado |
---|---|
Alice | Not About The Guitar |
Catalina | Vamos voltar a juntar-nos |
David | NULL |
Lea | NULL |
Marc | NULL |
A primeira coluna é preenchida a partir da tabela Singers
e a segunda coluna é preenchida a partir da tabela Songs
. Nos casos em que existia um SingerId
na tabela Singers
, mas não existia nenhum SingerId
correspondente na tabela Songs
, a segunda coluna contém NULL
.
Este é o plano de execução:
O nó de nível superior é um operador de união distribuída. O operador de união distribuída distribui subplanos para servidores remotos. O subplano contém um operador serialize result que calcula o nome próprio do cantor e o nome de uma das músicas do cantor e serializa cada linha do resultado.
O operador de resultado de serialização recebe a respetiva entrada de um operador de aplicação cruzada.
O lado de entrada do operador cross apply é uma análise da tabela Singers
.
O lado do mapa para a operação de aplicação cruzada contém o seguinte (de cima para baixo):
- Um operador agregado que devolve
Songs.SongName
. - Um operador limit que limita o número de músicas devolvidas a uma por cantor.
- Uma análise de índice no índice
SongsBySingerAlbumSongNameDesc
.
O operador de aplicação cruzada mapeia cada linha do lado de entrada para uma linha do lado de mapeamento que tenha o mesmo SingerId
. O resultado do operador de aplicação cruzada é o valor FirstName
da linha de entrada e o valor SongName
da linha de mapeamento.
(O valor SongName
é NULL
se não existir uma linha de mapeamento com correspondência em SingerId
.) O operador de união distribuída na parte superior do plano de execução combina todas as linhas de saída dos servidores remotos e devolve-as como os resultados da consulta.
Junção hash
Um operador hash join é uma implementação baseada em hash de junções SQL. As junções hash executam o processamento baseado em conjuntos. O operador de junção hash lê linhas da entrada marcada como build e insere-as numa tabela hash com base numa condição de junção. Em seguida, o operador de junção hash lê as linhas da entrada marcada como sonda. Para cada linha lida a partir da entrada da sonda, o operador de junção hash procura linhas correspondentes na tabela hash. O operador de junção hash devolve as linhas correspondentes como resultado.
Por exemplo, usando esta consulta:
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;
Estes são os resultados:
AlbumTitle | SongName |
---|---|
Nada a ver comigo | Not About The Guitar |
Verde | A segunda vez |
Verde | Recomeçar |
Verde | Nothing Is The Same |
Verde | Vamos voltar a juntar-nos |
Verde | I Knew You Were Magic |
Verde | Azul |
Verde | 42 |
Aterrorizado | História de luta |
Este é o plano de execução:
No plano de execução, build é uma união distribuída que distribui análises na tabela Albums
. Probe é um operador de união distribuído que distribui as análises no índice SongsBySingerAlbumSongNameDesc
.
O operador de junção hash lê todas as linhas do lado de criação. Cada linha de compilação é
colocada numa tabela de hash com base nas colunas na condição a.SingerId =
s.SingerId AND a.AlbumId = s.AlbumId
. Em seguida, o operador de junção hash lê todas as linhas do lado de sondagem. Para cada linha de sondagem, o operador de junção hash procura correspondências na tabela hash. As correspondências resultantes são devolvidas pelo operador de junção hash.
As correspondências resultantes na tabela de hash também podem ser filtradas por uma condição residual antes de serem devolvidas. (Um exemplo de onde aparecem condições residuais é em junções de não igualdade). Os planos de execução de junções hash podem ser complexos devido à gestão de memória e às variantes de junção. O algoritmo de junção hash principal é adaptado para processar variantes de junção interna, semi, anti e externa.
União de intercalação
Um operador de junção de união é uma implementação baseada na união da junção SQL. Ambos os lados da junção produzem linhas ordenadas pelas colunas usadas na condição de junção. A junção de união consome ambas as streams de entrada em simultâneo e gera linhas quando a condição de junção é satisfeita. Se as entradas não estiverem originalmente ordenadas conforme necessário, o otimizador adiciona operadores Sort
explícitos ao plano.
A junção de união não é selecionada automaticamente pelo otimizador. Para usar este operador, defina o método de junção como MERGE_JOIN
na sugestão de consulta, conforme mostrado no exemplo seguinte:
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;
Estes são os resultados:
AlbumTitle | SongName |
---|---|
Verde | A segunda vez |
Verde | Recomeçar |
Verde | Nothing Is The Same |
Verde | Vamos voltar a juntar-nos |
Verde | I Knew You Were Magic |
Verde | Azul |
Verde | 42 |
Aterrorizado | História de luta |
Nada a ver comigo | Not About The Guitar |
Este é o plano de execução:
Neste plano de execução, a junção de intercalação é distribuída para que a junção seja executada onde os dados estão localizados. Isto também permite que a junção de união neste exemplo funcione sem a introdução de operadores de ordenação adicionais, porque as análises de tabelas já estão ordenadas por SingerId
, AlbumId
, que é a condição de junção. Neste plano, a leitura do lado esquerdo da tabela Albums
avança sempre que o par SingerId
, AlbumId
for comparativamente inferior ao par SongsBySingerAlbumSongNameDesc
, SingerId_1
do índice de leitura do lado direito.AlbumId_1
Da mesma forma, o lado direito avança sempre que for inferior ao lado esquerdo. Esta união avançada continua a pesquisar equivalências para que possam ser devolvidas correspondências resultantes.
Considere outro exemplo de junção de união com a seguinte consulta:
SELECT a.albumtitle,
s.songname
FROM albums AS a join@{join_method=merge_join} songs AS s
ON a.albumid = s.albumid;
Produz os seguintes resultados:
AlbumTitle | SongName |
---|---|
Total de lixo | A segunda vez |
Total de lixo | Recomeçar |
Total de lixo | Nothing Is The Same |
Total de lixo | Vamos voltar a juntar-nos |
Total de lixo | I Knew You Were Magic |
Total de lixo | Azul |
Total de lixo | 42 |
Total de lixo | Not About The Guitar |
Verde | A segunda vez |
Verde | Recomeçar |
Verde | Nothing Is The Same |
Verde | Vamos voltar a juntar-nos |
Verde | I Knew You Were Magic |
Verde | Azul |
Verde | 42 |
Verde | Not About The Guitar |
Nada a ver comigo | A segunda vez |
Nada a ver comigo | Recomeçar |
Nada a ver comigo | Nothing Is The Same |
Nada a ver comigo | Vamos voltar a juntar-nos |
Nada a ver comigo | I Knew You Were Magic |
Nada a ver comigo | Azul |
Nada a ver comigo | 42 |
Nada a ver comigo | Not About The Guitar |
Reproduzir | A segunda vez |
Reproduzir | Recomeçar |
Reproduzir | Nothing Is The Same |
Reproduzir | Vamos voltar a juntar-nos |
Reproduzir | I Knew You Were Magic |
Reproduzir | Azul |
Reproduzir | 42 |
Reproduzir | Not About The Guitar |
Aterrorizado | História de luta |
Este é o plano de execução:
No plano de execução anterior, o otimizador de consultas introduziu operadores Sort
adicionais para alcançar as propriedades necessárias para a execução da junção de união. A condição JOIN
na consulta deste exemplo é apenas em
AlbumId
, que não é a forma como os dados são armazenados, pelo que tem de ser adicionada uma ordenação. O motor de consulta suporta um algoritmo de união distribuída, o que permite que a ordenação ocorra localmente em vez de globalmente, o que distribui e paraleliza o custo da CPU.
As correspondências resultantes também podem ser filtradas por uma condição residual antes de serem devolvidas. (Um exemplo de onde aparecem condições residuais é em junções de não igualdade). Os planos de execução de junções de união podem ser complexos devido a requisitos de ordenação adicionais. O algoritmo de junção de união principal é adaptado para processar variantes de junção interna, semi, anti e externa.
Junção de hash de transmissão de envio
Um operador push broadcast hash join é uma implementação distribuída baseada em hash-join de junções SQL. O operador de junção hash de transmissão push lê linhas do lado de entrada para construir um lote de dados. Em seguida, esse lote é transmitido a todos os servidores que contêm dados do lado do mapa. Nos servidores de destino onde o lote de dados é recebido, é criada uma associação hash com o lote como os dados do lado de criação e, em seguida, os dados locais são analisados como o lado de sondagem da associação hash.
A junção de hash de transmissão push não é selecionada automaticamente pelo otimizador. Para
usar este operador, defina o método de junção como PUSH_BROADCAST_HASH_JOIN
na dica de consulta, conforme mostrado no exemplo seguinte:
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;
Estes são os resultados:
AlbumTitle | SongName |
---|---|
Verde | A segunda vez |
Verde | Recomeçar |
Verde | Nothing Is The Same |
Verde | Lets Get Back Together |
Verde | I Knew You Were Magic |
Verde | Azul |
Verde | 42 |
Aterrorizado | História de luta |
Nada a ver comigo | Not About The Guitar |
Este é o plano de execução:
A entrada para a junção hash de transmissão push é o índice AlbumsByAlbumTitle
.
Essa entrada é serializada num lote de dados. Em seguida, esse lote é enviado para todas as divisões locais do índice SongsBySingerAlbumSongNameDesc
, onde o lote é desserializado e criado numa tabela hash. Em seguida, a tabela de hash usa os dados do índice local como uma sondagem que devolve as correspondências resultantes.
As correspondências resultantes também podem ser filtradas por uma condição residual antes de serem devolvidas. (Um exemplo de onde aparecem condições residuais é em junções de não igualdade).
Aplicação externa
Um operador outer apply é semelhante a um operador cross apply, exceto que um operador outer apply garante que cada execução no lado do mapa devolve, pelo menos, uma linha, criando uma linha com preenchimento NULL, se necessário. (Por outras palavras, fornece semântica de junção externa esquerda.)
União recursiva
Um operador de união recursiva executa uma união de duas entradas, uma que representa um caso base
e a outra que representa um caso recursive
. É usado em consultas de gráficos com travessias de caminhos quantificadas. A entrada base é processada
primeiro e exatamente uma vez. A entrada recursiva é processada até a recursão terminar. A recursão termina quando o limite superior, se especificado, for atingido ou quando a recursão não produzir novos resultados. No exemplo seguinte, a tabela Collaborations
é adicionada ao esquema e é criado um gráfico de propriedades denominado 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),
);
A seguinte consulta de grafos encontra cantores que colaboraram com um determinado cantor ou que colaboraram com esses colaboradores.
GRAPH MusicGraph
MATCH (singer:Singers {singerId:42})-[c:CollabWith]->{1,2}(featured:Singers)
RETURN singer.SingerId AS singer, featured.SingerId AS featured
O operador recursive union filtra a tabela Singers
para encontrar o cantor com o SingerId
especificado. Esta é a entrada base para a união recursiva. A entrada recursiva para a união recursiva compreende uma aplicação cruzada distribuída ou outro operador de junção para outras consultas que junta repetidamente a tabela Collaborations
com os resultados da iteração anterior da junção. As linhas da entrada base formam a iteração zero. Em cada iteração, a saída da iteração é armazenada pela análise recursiva de spool. As linhas da análise de spool recursiva são unidas com a tabela Collaborations
em spoolscan.featuredSingerId =
Collaborations.SingerId
. A recursão termina quando duas iterações estão
concluídas, uma vez que esse é o limite superior especificado na consulta.
Operadores de n-aridade
Um operador N-ário é um operador que tem mais de dois filhos relacionais. Os seguintes operadores são operadores N-ários:
União de tudo
Um operador union all combina todos os conjuntos de linhas dos respetivos elementos secundários sem remover duplicados. Todos os operadores de união recebem a respetiva entrada de operadores union input que são distribuídos por vários servidores. O operador union all requer que as respetivas entradas tenham o mesmo esquema, ou seja, o mesmo conjunto de tipos de dados para cada coluna.
Por exemplo, usando esta consulta:
SELECT 1 a,
2 b
UNION ALL
SELECT 3 a,
4 b
UNION ALL
SELECT 5 a,
6 b;
O tipo de linha para os elementos secundários consiste em dois números inteiros.
Estes são os resultados:
a | b |
---|---|
1 | 2 |
3 | 4 |
5 | 6 |
Este é o plano de execução:
O operador union all combina as respetivas linhas de entrada e, neste exemplo, envia os resultados para um operador serialize result.
Uma consulta como a seguinte teria êxito, porque é usado o mesmo conjunto de tipos de dados para cada coluna, mesmo que os elementos secundários usem variáveis diferentes para os nomes das colunas:
SELECT 1 a,
2 b
UNION ALL
SELECT 3 c,
4 e;
Uma consulta como a seguinte não seria bem-sucedida porque os elementos subordinados usam tipos de dados diferentes para as colunas:
SELECT 1 a,
2 b
UNION ALL
SELECT 3 a,
'This is a string' b;
Subconsultas escalares
Uma subconsulta escalar é uma subexpressão SQL que faz parte de uma expressão escalar. O Spanner tenta remover subconsultas escalares sempre que possível. No entanto, em determinados cenários, os planos podem conter explicitamente subconsultas escalares.
Por exemplo, usando esta consulta:
SELECT firstname,
IF(firstname = 'Alice', (SELECT Count(*)
FROM songs
WHERE duration > 300), 0)
FROM singers;
Esta é a subexpressão SQL:
SELECT Count(*)
FROM songs
WHERE duration > 300;
Estes são os resultados (da consulta completa):
FirstName | |
---|---|
Alice | 1 |
Catalina | 0 |
David | 0 |
Lea | 0 |
Marc | 0 |
Este é o plano de execução:
O plano de execução contém uma subconsulta escalar, apresentada como Subconsulta escalar, sobre um operador aggregate.
Por vezes, o Spanner converte subconsultas escalares noutro operador, como uma junção ou uma aplicação cruzada, para possivelmente melhorar o desempenho.
Por exemplo, usando esta consulta:
SELECT *
FROM songs
WHERE duration = (SELECT Max(duration)
FROM songs);
Esta é a subexpressão SQL:
SELECT MAX(Duration)
FROM Songs;
Estes são os resultados (da consulta completa):
SingerId | AlbumId | TrackId | SongName | Duração | SongGenre |
---|---|---|---|---|---|
2 | 1 | 6 | Nothing Is The Same | 303 | BLUES |
Este é o plano de execução:
O plano de execução não contém uma subconsulta escalar porque o Spanner converteu a subconsulta escalar numa aplicação cruzada.
Subconsultas de matrizes
Uma subconsulta de matriz é semelhante a uma subconsulta escalar, exceto que a subconsulta pode consumir mais do que uma linha de entrada. As linhas consumidas são convertidas numa matriz de saída escalar única que contém um elemento por linha de entrada consumida.
Por exemplo, usando esta consulta:
SELECT a.albumid,
array
(
select concertdate
FROM concerts
WHERE concerts.singerid = a.singerid)
FROM albums AS a;
Esta é a subconsulta:
SELECT concertdate
FROM concerts
WHERE concerts.singerid = a.singerid;
Os resultados da subconsulta para cada AlbumId
são convertidos numa matriz de linhas em função desse AlbumId
.ConcertDate
O plano de execução contém uma subconsulta de matriz, apresentada como Subconsulta de matriz, acima de um operador de união distribuído:
Operadores distribuídos
Os operadores descritos anteriormente nesta página são executados dentro dos limites de uma única máquina. Os operadores distribuídos são executados em vários servidores.
Os seguintes operadores são operadores distribuídos:
- União distribuída
- União de união distribuída
- Aplicação cruzada distribuída
- Aplicação externa distribuída
- Aplique mutações
O operador de união distribuído é o operador primitivo a partir do qual são derivados o cross apply distribuído e o outer apply distribuído.
Os operadores distribuídos aparecem nos planos de execução com uma variante de união distribuída no topo de uma ou mais variantes de união distribuída local. Uma variante de união distribuída executa a distribuição remota de subplanos. Uma variante de união distribuída local está no topo de cada uma das análises realizadas para a consulta, conforme mostrado neste plano de execução:
As variantes de união distribuídas locais garantem uma execução de consultas estável quando ocorrem reinícios para limites de divisão que mudam dinamicamente.
Sempre que possível, uma variante de união distribuída tem um predicado dividido que resulta na eliminação de divisões, o que significa que os servidores remotos executam subplanos apenas nas divisões que satisfazem o predicado. Isto melhora a latência e o desempenho geral das consultas.
União distribuída
Um operador de união distribuída divide conceptualmente uma ou mais tabelas em várias divisões, avalia remotamente uma subconsulta de forma independente em cada divisão e, em seguida, une todos os resultados.
Por exemplo, usando esta consulta:
SELECT s.songname,
s.songgenre
FROM songs AS s
WHERE s.singerid = 2
AND s.songgenre = 'ROCK';
Estes são os resultados:
SongName | SongGenre |
---|---|
Recomeçar | ROCK |
A segunda vez | ROCK |
História de luta | ROCK |
Este é o plano de execução:
O operador de união distribuída envia subplanos para servidores remotos, que executam uma análise de tabelas em divisões que satisfazem o predicado da consulta WHERE
s.SingerId = 2 AND s.SongGenre = 'ROCK'
. Um operador serialize
result calcula os valores SongName
e SongGenre
a partir das linhas devolvidas pelas análises de tabelas. O operador de união distribuída devolve os resultados combinados dos servidores remotos como os resultados da consulta SQL.
União de intercalação distribuída
O operador distributed merge union distribui uma consulta por vários servidores remotos. Em seguida, combina os resultados da consulta para produzir um resultado ordenado, conhecido como ordenação por intercalação distribuída.
Uma união de união distribuída executa os seguintes passos:
O servidor raiz envia uma subconsulta a cada servidor remoto que aloja uma divisão dos dados consultados. A subconsulta inclui instruções que os resultados são ordenados por uma ordem específica.
Cada servidor remoto executa a subconsulta na respetiva divisão e, em seguida, envia os resultados na ordem pedida.
O servidor raiz une a subconsulta ordenada para produzir um resultado completamente ordenado.
A união de união distribuída está ativada por predefinição para a versão 3 e posteriores do Spanner.
Aplicação cruzada distribuída
Um operador de aplicação cruzada distribuída (DCA) expande o operador de aplicação cruzada através da execução em vários servidores. O lado de entrada do DCA agrupa lotes de linhas (ao contrário de um operador de aplicação cruzada normal, que atua apenas numa linha de entrada de cada vez). O lado do mapa da DCA é um conjunto de operadores de aplicação cruzada que são executados em servidores remotos.
Por exemplo, usando esta consulta:
SELECT albumtitle
FROM songs
JOIN albums
ON albums.albumid = songs.albumid;
Os resultados estão no formato:
AlbumTitle |
---|
Verde |
Nada a ver comigo |
Reproduzir |
Total de lixo |
Verde |
Este é o plano de execução:
A entrada de DCA contém um índice scan no índice SongsBySingerAlbumSongNameDesc
que agrupa linhas de AlbumId
. O lado do mapa para este operador de aplicação cruzada é uma análise de índice no índice AlbumsByAlbumTitle
, sujeito ao predicado de AlbumId
na linha de entrada que corresponde à chave AlbumId
no índice AlbumsByAlbumTitle
. O mapeamento devolve o SongName
para os valores SingerId
nas linhas de entrada em lote.
Para resumir o processo de DCA neste exemplo, a entrada do DCA são as linhas
em lote da tabela Albums
e a saída do DCA é a aplicação destas
linhas ao mapa da leitura do índice.
Aplicação externa distribuída
Um operador distributed outer apply expande o operador outer apply através da execução em vários servidores, de forma semelhante à forma como um operador distributed cross apply expande um operador cross apply.
Por exemplo, usando esta consulta:
SELECT lastname,
concertdate
FROM singers LEFT OUTER join@{JOIN_TYPE=APPLY_JOIN} concerts
ON singers.singerid=concerts.singerid;
Os resultados estão no formato:
LastName | ConcertDate |
---|---|
Trentor | 2014-02-18 |
Smith | 2011-09-03 |
Smith | 2010-06-06 |
Lomond | 2005-04-30 |
Martin | 2015-11-04 |
Richards |
Este é o plano de execução:
Aplique mutações
Um operador apply mutations aplica as mutações de uma declaração de manipulação de dados (DML) à tabela. É o operador principal num plano de consulta para uma declaração DML.
Por exemplo, usando esta consulta:
DELETE FROM singers
WHERE firstname = 'Alice';
Estes são os resultados:
4 rows deleted This statement deleted 4 rows and did not return any rows.
Este é o plano de execução:
Informações adicionais
Esta secção descreve itens que não são operadores autónomos, mas que, em vez disso, executam tarefas para suportar um ou mais dos operadores listados anteriormente. Os itens descritos aqui são tecnicamente operadores, mas não são operadores separados no seu plano de consulta.
Construtor de struct
Um construtor de struct cria um struct ou uma coleção de campos. Normalmente, cria uma struct para linhas resultantes de uma operação de cálculo. Um construtor de struct não é um operador autónomo. Em alternativa, aparece nos operadores compute struct ou nos operadores serialize result.
Para uma operação de estrutura de cálculo, o construtor de estruturas cria uma estrutura para que as colunas das linhas calculadas possam usar uma única referência de variável à estrutura.
Para uma operação de resultado de serialização, o construtor de struct cria um struct para serializar os resultados.
Por exemplo, usando esta consulta:
SELECT IF(TRUE, struct(1 AS A, 1 AS B), struct(2 AS A , 2 AS B)).A;
Estes são os resultados:
A |
---|
1 |
Este é o plano de execução:
No plano de execução, os construtores de estruturas aparecem dentro de um operador de resultado de serialização.