Cliente
O Spanner oferece suporte a consultas SQL. Veja a seguir uma consulta de amostra:
SELECT s.SingerId, s.FirstName, s.LastName, s.SingerInfo
FROM Singers AS s
WHERE s.FirstName = @firstName;
A construção @firstName
é uma referência a um parâmetro de consulta. Você pode usar um parâmetro de consulta em qualquer lugar em que um valor literal possa ser usado. O uso de parâmetros em APIs programáticas é recomendado. O uso de parâmetros de consulta ajuda a evitar ataques de injeção de SQL, e as consultas resultantes são mais propensas a se beneficiar de vários caches do lado do servidor. Consulte Como armazenar em cache, abaixo.
Os parâmetros de consulta precisam estar limitados a um valor quando a consulta é executada. Exemplo:
Statement statement =
Statement.newBuilder("SELECT s.SingerId...").bind("firstName").to("Jimi").build();
try (ResultSet resultSet = dbClient.singleUse().executeQuery(statement)) {
while (resultSet.next()) {
...
}
}
Depois que o Spanner recebe uma chamada de API, ele analisa a consulta e limita os parâmetros para determinar qual node do servidor do Spanner processará a consulta. O servidor envia um stream das linhas de resultados que são consumidas pelas chamadas para ResultSet.next()
.
Execução da consulta
A execução da consulta começa com a chegada de uma solicitação de "executar consulta" em algum servidor do Spanner. O servidor executa as seguintes etapas:
- Validação do pedido
- Análise do texto da consulta
- Geração de uma álgebra de consulta inicial
- Geração de uma álgebra de consulta otimizada
- Geração de um plano de consulta executável
- Execução do plano (verificação das permissões, leitura dos dados, codificação dos resultados etc.)
Análise
O analisador SQL analisa o texto da consulta e o converte em uma árvore de sintaxe abstrata. Ele extrai a estrutura de consulta básica (SELECT …
FROM … WHERE …)
e faz verificações sintáticas.
Álgebra
O sistema de tipos do Spanner pode representar escalares, matrizes, estruturas etc. A álgebra de consulta define operadores para verificações de tabelas, filtragem, classificação/agrupamento, todos os tipos de junções, agregação e muito mais. A álgebra de consulta inicial é criada da saída do analisador. As referências de nomes de campo na árvore de análise são resolvidas com o esquema do banco de dados. Esse código também verifica erros de semântica, como número incorreto de parâmetros, incompatibilidades na digitação etc.
A próxima etapa ("otimização da consulta") usa a álgebra inicial e gera uma álgebra otimizada. Isso pode ser mais simples, mais eficiente ou mais adequado às capacidades do mecanismo de execução. Por exemplo, a álgebra inicial pode especificar apenas uma "junção", enquanto a álgebra otimizada especifica uma "junção de hash".
Execução
O plano de consulta executável final é construído da álgebra reescrita. Basicamente, o plano executável é um gráfico acíclico direcionado de "iteradores". Cada iterador expõe uma sequência de valores. Os iteradores podem utilizar entradas para produzir saídas (por exemplo, iterador de classificação). As consultas que envolvem uma única divisão podem ser executadas por um único servidor (o que mantém os dados). O servidor verificará os intervalos de várias tabelas, executará junções, realizará a agregação e fará todas as outras operações definidas pela álgebra de consulta.
As consultas que envolvem várias divisões serão fatoradas em vários pedaços. Uma parte da consulta continuará sendo executada no servidor principal (raiz). Outras subconsultas parciais são transferidas para nodes de folha (aqueles que contêm as divisões sendo lidas). Essa entrega pode ser recursivamente aplicada a consultas complexas, resultando em uma árvore de execuções de servidor. Em todos os servidores, há um consentimento em relação a um carimbo de data/hora para que os resultados da consulta sejam um instantâneo consistente dos dados. Cada servidor de folha envia um stream dos resultados parciais. Para consultas envolvendo agregação, podem ser resultados parcialmente agregados. O servidor raiz da consulta processa os resultados dos servidores de folha e executa o restante do plano de consulta. Para mais informações, consulte Planos de execução de consulta.
Quando uma consulta envolve várias divisões, o Spanner pode executar a consulta em paralelo nas divisões. O grau de paralelismo depende do intervalo de dados que a consulta verifica, do plano de execução da consulta e da distribuição de dados entre as divisões. O Spanner define automaticamente o grau máximo de paralelismo para uma consulta com base no tamanho e na configuração da instância (regional ou multirregional) para alcançar o desempenho ideal de consultas e evitar sobrecarregar a CPU.
Armazenamento em cache
Muitos dos artefatos do processamento de consulta são automaticamente armazenados em cache e reutilizados para consultas subsequentes. Isso inclui álgebras de consulta, planos de consulta executáveis etc. O armazenamento em cache é baseado no texto da consulta, nomes e tipos de parâmetros vinculados, e assim por diante. É por isso é melhor usar parâmetros associados (como @firstName
, no exemplo acima) em vez de valores literais no texto da consulta. O primeiro pode ser armazenado em cache uma vez e reutilizado independentemente do valor vinculado real. Consulte
Como otimizar o desempenho da consulta do Spanner para mais detalhes.
Tratamento de erros
O fluxo de linhas de resultado do método executeQuery
pode ser interrompido por
vários motivos: erros de rede temporários, transferência de uma divisão
de um servidor para outro (por exemplo, balanceamento de carga), reinicializações do servidor (por exemplo,
upgrade para uma nova versão) etc. Para ajudar a recuperar esses erros,
o Spanner envia "tokens de retomada" opacos com lotes de dados de resultados
parciais. Esses tokens de retomada podem ser usados ao repetir a consulta para continuar
de onde parou a consulta interrompida. Se você estiver usando o Spanner
bibliotecas de cliente, isso é feito automaticamente. Assim, os usuários da biblioteca de cliente
não se preocupe com esse tipo de falha temporária.