Planos de execução de consultas

Introdução

Esta página contém conceitos sobre planos de execução de consulta e como eles são usados pelo Cloud Spanner para executar consultas em um ambiente distribuído. Para saber como recuperar um plano de execução para uma consulta específica usando o Console do GCP, consulte Noções básicas sobre como o Cloud Spanner executa consultas.

O Cloud Spanner usa instruções SQL declarativas para consultar os bancos de dados. As instruções SQL definem o que o usuário quer sem especificar como conseguir os resultados. Um plano de execução de consulta é o conjunto de etapas para se conseguir os resultados. Para uma determinada instrução SQL, pode haver várias maneiras de conseguir os resultados. O compilador de consulta do Cloud Spanner avalia as diferentes maneiras de produzir um plano de execução de consulta que é considerado o mais eficiente. Em seguida, o Cloud Spanner usa o plano de execução para recuperar os resultados.

Conceitualmente, um plano de execução é uma árvore de operadores relacionais. Cada operador lê as linhas de entrada e produz linhas de saída. O resultado do operador na raiz da execução é retornado como resultado da consulta SQL.

Como exemplo, esta consulta:

SELECT s.SongName FROM Songs AS s;

resulta em um plano de execução de consulta que pode ser visualizado como:

Exemplo de plano de execução de consulta

As consultas e os planos de execução neste tópico são baseados no seguinte esquema de banco 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);

Você pode executar consultas e recuperar planos de execução, mesmo que as tabelas não tenham dados.

Conseguir planos de execução eficientes é um desafio porque o Cloud Spanner separa os dados em divisões. As divisões podem ser movimentadas independentemente entre si e atribuídas a servidores diferentes, que podem estar em diferentes locais físicos. Para avaliar os planos de execução sobre os dados distribuídos, o Cloud Spanner usa a execução com base em:

  • execução local de subplanos em servidores que contêm os dados;
  • orquestração e agregação de várias execuções remotas com poda agressiva por distribuição.

O Cloud Spanner usa a operador primitivo distributed union, com suas variantes distributed cross apply e distributed outer apply, para ativar esse modelo.

Vida útil de uma consulta

Uma consulta SQL no Cloud Spanner primeiro é compilada em um plano de execução, depois é enviada a um servidor raiz inicial para execução. O servidor raiz é escolhido de modo a minimizar o número de saltos para alcançar os dados que estão sendo consultados. O servidor raiz então:

  • inicia a execução remota de subplanos (se necessário);
  • aguarda os resultados das execuções remotas;
  • processas as etapas de execução local restantes, como a agregação de resultados;
  • retorna os resultados da consulta.

Os servidores remotos que recebem um subplano atuam como servidor "raiz" para o subplano, seguindo o mesmo modelo que o servidor raiz superior. O resultado é uma árvore de execuções remotas. Conceitualmente, a execução da consulta flui de cima para baixo e os resultados da consulta são retornados de baixo para cima.O diagrama a seguir mostra esse padrão:

Plano de consulta conceitual

Os exemplos a seguir ilustram esse padrão com mais detalhes.

Consultas agregadas

Uma consulta agregada implementa consultas GROUP BY.

Por exemplo, para esta consulta:

SELECT s.SingerId, COUNT(*) AS SongCount
FROM Songs AS s
WHERE s.SingerId < 100
GROUP BY s.SingerId;

Os resultados são os seguintes:

+----------+-----------+
| SingerId | SongCount |
+----------+-----------+
|        3 |         1 |
|        2 |         8 |
+----------+-----------+

Conceitualmente, o plano de execução é o seguinte:

Plano de execução da consulta de agregado

O Cloud Spanner envia o plano de execução a um servidor raiz que coordena a execução da consulta e executa a distribuição remota dos subplanos.

Esse plano de execução começa com uma união distribuída, que distribui subplanos a servidores remotos com divisões que atendem a SingerId < 100. As uniões distribuídas locais, mostradas posteriormente no plano, representam a execução nos servidores remotos. Cada união distribuída local avalia uma subconsulta de modo independente nas divisões da tabela Songs, sujeita ao filtro SingerId < 100. As uniões distribuídas locais retornam resultados para um operador aggregate. O operador "aggregate" executa a agregação de COUNT por SingerId e retorna os resultados a um operador serialize result. O operador serialize result serializa os resultados em linhas que contêm a contagem de canções por SingerId. Em seguida, a união distribuída une todos os resultados e retorna os resultados da consulta.

Saiba mais sobre agregados em operador aggregate.

Consultas de junções colocalizadas

As tabelas intercaladas são armazenadas fisicamente com as respectivas linhas de tabelas relacionadas no mesmo lugar. Uma junção colocalizada é uma junção entre tabelas intercaladas. As junções colocalizadas podem oferecer melhores benefícios de desempenho do que as junções que exigem índices ou junções de volta.

Por exemplo, para esta consulta:

SELECT al.AlbumTitle, so.SongName
FROM Albums AS al, Songs AS so
WHERE al.SingerId = so.SingerId AND al.AlbumId = so.AlbumId;

Nesta consulta, supomos que Songs é intercalado em Albums.

Os resultados são os seguintes:

+-----------------------+--------------------------+
| AlbumTitle            | SongName                 |
+-----------------------+--------------------------+
| Nothing To Do With Me | Not About The Guitar     |
| Green                 | The Second Time          |
| Green                 | Starting Again           |
| Green                 | Nothing Is The Same      |
| Green                 | Let Us Get Back Together |
| Green                 | I Knew You Were Magic    |
| Green                 | Blue                     |
| Green                 | 42                       |
| Terrified             | Fight Story              |
+-----------------------+--------------------------+

O plano de execução é o seguinte:

Plano de execução de consulta colocalizada

Esse plano de execução começa com uma união distribuída, que distribui subplanos a servidores remotos que têm divisões da tabela Albums. Como Songs é uma tabela intercalada de Albums, cada servidor remoto pode executar todo o subplano em cada servidor remoto sem exigir uma junção a um servidor diferente.

Os subplanos contêm um cross apply. Cada cross apply executa uma verificação na tabela Albums para recuperar SingerId, AlbumId e AlbumTitle. Em seguida, o cross apply associa a saída da verificação de tabela à saída de uma verificação do índice SongsBySingerAlbumSongNameDesc, sujeito a um filtro do SingerId no índice que corresponde ao SingerId da saída da verificação de tabela. Cada cross apply envia os resultados a um operador serialize result, que serializa os dados de AlbumTitle e SongName e os retorna para as uniões distribuídas locais. Os agregados de união distribuída resultam das uniões distribuídas locais e as retorna como resultado da consulta.

Consultas de índice e junção de volta

O exemplo acima usou uma junção em duas tabelas, uma intercalada à outra. Os planos de execução são mais complexos e menos eficientes quando duas tabelas, ou uma tabela e um índice, não são intercalados.

Considere um índice criado com o seguinte comando:

CREATE INDEX SongsBySongName ON Songs(SongName)

Use este índice nesta consulta:

SELECT s.SongName, s.Duration
FROM Songs@{force_index=SongsBySongName} AS s
WHERE STARTS_WITH(s.SongName, "B");

Os resultados são os seguintes:

+----------+----------+
| SongName | Duration |
+----------+----------+
| Blue     |      238 |
+----------+----------+

O plano de execução é o seguinte:

Plano de execução da consulta de junção de volta

O plano de execução resultante é complicado porque o índice SongsBySongName não contém a coluna Duration. Para conseguir o valor de Duration, o Cloud Spanner precisa fazer a junção de volta dos resultados indexados para a tabela Songs. Trata-se de uma junção, mas não colocalizada, porque a tabela Songs e o índice global SongsBySongName não são intercalados. O plano de execução resultante é mais complexo do que o exemplo de junção colocalizada, porque o Cloud Spanner executa otimizações para acelerar a execução quando os dados não estão colocalizados.

O operador superior é um distributed cross apply. O lado de entrada do operador é formado por lotes de linhas do índice SongsBySongName que satisfazem o predicado STARTS_WITH(s.SongName, "B"). Em seguida, o distributed cross apply mapeia esses lotes para servidores remotos cujas divisões contêm os dados de Duration. Os servidores remotos usam uma verificação de tabela para recuperar a coluna Duration. A verificação de tabela usa o filtro Condition:($Songs_key_TrackId' = $batched_Songs_key_TrackId), que junta TrackId da tabela Songs com TrackId das linhas que foram reunidas em lote do índice SongsBySongName.

Os resultados são agregados na resposta final da consulta. Por sua vez, o lado input do distributed cross apply contém um par de união distribuída/união distribuída local para avaliar as linhas do índice que satisfazem ao predicado STARTS_WITH.

Considere uma consulta ligeiramente diferente que não seleciona a coluna s.Duration:

SELECT s.SongName
FROM Songs@{force_index=SongsBySongName} AS s
WHERE STARTS_WITH(s.SongName, "B");

Essa consulta é capaz de aproveitar totalmente o índice, conforme mostrado neste plano de execução:

Plano de execução de consulta mais simples

O plano de execução não requer uma junção de volta porque todas as colunas solicitadas pela consulta estão presentes no índice.

Próximas etapas

Esta página foi útil? Conte sua opinião sobre:

Enviar comentários sobre…

Documentação do Cloud Spanner