Cliente
O Spanner é compatível com 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. Para mais informações, consulte
Como armazenar em cache.
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 da API, ele analisa a consulta e os parâmetros vinculados para determinar qual nó do servidor do Spanner vai 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 consultas.
Quando uma consulta envolve várias divisões, o Spanner pode executá-la em paralelo entre elas. 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 da instância e na configuração da instância (regional ou multirregional) para alcançar o desempenho ideal da consulta 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. É 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
Os métodos executeQuery (ou executeStreamingSql) e streamingRead retornam um fluxo de mensagens PartialResultSet. Para eficiência, um único valor de linha ou coluna pode ser dividido em várias mensagens PartialResultSet, principalmente para dados grandes.
Esse fluxo pode ser interrompido por erros transitórios de rede, transferências divididas ou reinicializações do servidor. As transferências divididas podem ocorrer durante o balanceamento de carga, e as reinicializações do servidor podem ocorrer durante os upgrades.
Para lidar com essas interrupções, o Spanner inclui strings resume_token opacas em algumas mensagens PartialResultSet.
Pontos principais sobre resume_token:
- Nem todo
PartialResultSetcontém umresume_token. - Um
resume_tokengeralmente é incluído apenas no final de uma linha completa, marcando um ponto de retomada seguro. PartialResultSetcom umchunked_value(para valores grandes divididos em mensagens) não terá umresume_tokenaté que o valor e a linha inteiros sejam enviados.- Para retomar um stream interrompido, envie uma nova solicitação com o
resume_tokenlast received não vazio.
As bibliotecas de cliente do Spanner gerenciam automaticamente esse buffer e a recuperação. Eles montam linhas completas de mensagens PartialResultSet e rastreiam o resume_token mais recente. Se a conexão cair, a biblioteca usará
o último token válido para reiniciar o stream, descartando todos os dados parciais
recebidos após esse token. Esse processo garante que você veja um fluxo contínuo e sem duplicações de linhas completas, mesmo que ocorram falhas temporárias.