Otimize o cálculo de consultas
Este documento fornece as práticas recomendadas para otimizar o desempenho das consultas.
Quando executa uma consulta, pode
ver o plano de consulta
na Trusted Cloud consola. Também pode pedir detalhes de execução através das
INFORMATION_SCHEMA.JOBS*
visualizações
ou do
jobs.get
método da API REST.
O plano de consulta inclui detalhes sobre as fases e os passos da consulta. Estes detalhes podem ajudar a identificar formas de melhorar o desempenho das consultas. Por exemplo, se reparar que uma fase escreve muito mais resultados do que outras fases, pode significar que tem de filtrar mais cedo na consulta.
Para saber mais sobre o plano de consulta e ver exemplos de como as informações do plano de consulta podem ajudar a melhorar o desempenho das consultas, consulte o artigo Obtenha estatísticas de desempenho das consultas. Depois de resolver os problemas de desempenho das consultas, pode otimizar ainda mais a consulta realizando as seguintes tarefas:
- Reduza os dados a serem processados
- Otimize as operações de consulta
- Reduza o resultado da sua consulta
- Evite padrões anti-SQL
Reduza os dados tratados
Pode reduzir os dados que precisam de ser processados através das opções descritas nas secções seguintes.
Evite SELECT *
Prática recomendada: controle a projeção consultando apenas as colunas de que precisa.
A projeção refere-se ao número de colunas que são lidas pela sua consulta. A projeção de colunas em excesso incorre em E/S e materialização adicionais (desperdiçadas) (escrita de resultados).
- Use as opções de pré-visualização de dados. Se estiver a experimentar ou a explorar dados, use uma das
opções de pré-visualização de dados em vez de
SELECT *
. - Consultar colunas específicas. A aplicação de uma cláusula
LIMIT
a uma consultaSELECT *
não afeta a quantidade de dados lidos. A leitura de todos os bytes na tabela completa é faturada, e a consulta é contabilizada para a quota do nível gratuito. Em vez disso, consulte apenas as colunas de que precisa. Por exemplo, useSELECT * EXCEPT
para excluir uma ou mais colunas dos resultados. - Use tabelas particionadas. Se precisar de consultas em todas as colunas de uma tabela, mas apenas num subconjunto de dados, considere o seguinte:
- Materializar os resultados numa tabela de destino e consultar essa tabela.
- Particionar as tabelas
e consultar a partição relevante.
Por exemplo, use
WHERE _PARTITIONDATE="2017-01-01"
para consultar apenas a partição de 1 de janeiro de 2017.
Use
SELECT * EXCEPT
. Consultar um subconjunto de dados ou usarSELECT * EXCEPT
pode reduzir significativamente a quantidade de dados lidos por uma consulta. Além da redução de custos, o desempenho é melhorado através da redução da quantidade de E/S de dados e da quantidade de materialização necessária para os resultados da consulta.SELECT * EXCEPT (col1, col2, col5) FROM mydataset.newtable
Evite tabelas com carateres universais excessivos
Prática recomendada: quando consultar tabelas de carateres universais, tem de usar o prefixo mais detalhado.
Use carateres universais para consultar várias tabelas com instruções SQL concisas. As tabelas com carateres universais são uma união de tabelas que correspondem à expressão com carateres universais. As tabelas com carateres universais são úteis se o seu conjunto de dados contiver os seguintes recursos:
- Várias tabelas com nomes semelhantes e esquemas compatíveis
- Tabelas fragmentadas
Quando consulta uma tabela de carateres universais, especifique um caráter universal (*
) após o prefixo da tabela comum. Por exemplo, FROM
consulta todas as tabelas da década de 1940.bigquery-public-data.noaa_gsod.gsod194*
Os prefixos mais detalhados têm um desempenho melhor do que os prefixos mais curtos. Por exemplo,
FROM
tem um desempenho melhor do que
bigquery-public-data.noaa_gsod.gsod194*
FROM
porque menos tabelas correspondem
ao caráter universal.bigquery-public-data.noaa_gsod.*
Evite tabelas divididas por data
Prática recomendada: não use tabelas divididas por data (também denominadas tabelas com nome de data) em vez de tabelas com partição de tempo.
As tabelas particionadas têm um desempenho melhor do que as tabelas com nomes de datas. Quando cria tabelas divididas por data, o BigQuery tem de manter uma cópia do esquema e dos metadados para cada tabela com nome de data. Além disso, quando são usadas tabelas com nomes de datas, o BigQuery pode ter de validar as autorizações para cada tabela consultada. Esta prática também aumenta a sobrecarga de consultas e afeta o desempenho das consultas.
Evite a divisão excessiva de tabelas
Prática recomendada: evite criar demasiados fragmentos de tabelas. Se estiver a dividir tabelas por data, use tabelas particionadas por tempo.
A divisão de tabelas refere-se à divisão de grandes conjuntos de dados em tabelas separadas e à adição de um sufixo a cada nome de tabela. Se estiver a dividir tabelas por data, use tabelas particionadas por tempo em alternativa.
Devido ao baixo custo do armazenamento do BigQuery, não precisa de otimizar as tabelas em função do custo, como faria num sistema de base de dados relacional. A criação de um grande número de fragmentos de tabelas tem impactos no desempenho que superam quaisquer vantagens de custo.
As tabelas divididas requerem que o BigQuery mantenha o esquema, os metadados e as autorizações para cada divisão. Devido à sobrecarga adicional necessária para manter informações em cada fragmento, a fragmentação excessiva de tabelas pode afetar o desempenho das consultas.
A quantidade e a origem dos dados lidos por uma consulta podem afetar o desempenho e o custo da consulta.
Reduza as consultas particionadas
Prática recomendada: quando consultar uma tabela particionada, para filtrar com partições em tabelas particionadas, use as seguintes colunas:
- Para tabelas particionadas por tempo de ingestão, use a pseudocoluna
_PARTITIONTIME
- Para tabelas particionadas, como as baseadas em colunas de unidades de tempo e intervalos de números inteiros, use a coluna de partição.
Para tabelas particionadas por unidades de tempo, a filtragem de dados com _PARTITIONTIME
ou a coluna de particionamento permite-lhe especificar uma data ou um intervalo de datas. Por exemplo, a cláusula WHERE
seguinte usa a pseudocoluna _PARTITIONTIME
para especificar partições entre 1 de janeiro de 2016 e 31 de janeiro de 2016:
WHERE _PARTITIONTIME
BETWEEN TIMESTAMP("20160101")
AND TIMESTAMP("20160131")
A consulta processa os dados apenas nas partições indicadas pelo intervalo de datas. Filtrar as partições melhora o desempenho das consultas e reduz os custos.
Reduza os dados antes de usar um JOIN
Prática recomendada: reduza a quantidade de dados processados antes de uma cláusula JOIN
efetuando agregações.
A utilização de uma cláusula GROUP BY
com funções de agregação é computacionalmente intensiva, uma vez que estes tipos de consultas usam a função shuffle.
Como estas consultas exigem muitos cálculos, tem de usar uma cláusula GROUP BY
apenas quando for necessário.
Para consultas com GROUP BY
e JOIN
, faça a agregação mais cedo na consulta
para reduzir a quantidade de dados processados.
Por exemplo, a seguinte consulta executa uma JOIN
em duas tabelas grandes sem qualquer filtragem prévia:
WITH users_posts AS ( SELECT * FROM `bigquery-public-data`.stackoverflow.comments AS c JOIN `bigquery-public-data`.stackoverflow.users AS u ON c.user_id = u.id ) SELECT user_id, ANY_VALUE(display_name) AS display_name, ANY_VALUE(reputation) AS reputation, COUNT(text) AS comments_count FROM users_posts GROUP BY user_id ORDER BY comments_count DESC LIMIT 20;
Esta consulta pré-agrega as contagens de comentários, o que reduz a quantidade de dados
lidos para o JOIN
:
WITH comments AS ( SELECT user_id, COUNT(text) AS comments_count FROM `bigquery-public-data`.stackoverflow.comments WHERE user_id IS NOT NULL GROUP BY user_id ORDER BY comments_count DESC LIMIT 20 ) SELECT user_id, display_name, reputation, comments_count FROM comments JOIN `bigquery-public-data`.stackoverflow.users AS u ON user_id = u.id ORDER BY comments_count DESC;
Use a cláusula WHERE
Prática recomendada: use uma
cláusulaWHERE
para limitar a quantidade de dados devolvidos por uma consulta. Sempre que possível, use as colunas BOOL
, INT64
, FLOAT64
ou DATE
na cláusula WHERE
.
As operações nas colunas BOOL
, INT64
, FLOAT64
e DATE
são normalmente mais rápidas do que as operações nas colunas STRING
ou BYTE
. Sempre que possível, use uma coluna que use um destes tipos de dados na cláusula WHERE
para reduzir a quantidade de dados devolvidos pela consulta.
Use vistas materializadas
Prática recomendada: use vistas materializadas para pré-calcular os resultados de uma consulta para aumentar o desempenho e a eficiência.
As vistas materializadas são vistas pré-calculadas que armazenam em cache periodicamente os resultados de uma consulta para aumentar o desempenho e a eficiência. O BigQuery tira partido dos resultados pré-calculados das vistas materializadas e, sempre que possível, lê apenas as alterações das tabelas base para calcular resultados atualizados. Pode consultar diretamente as vistas materializadas ou o otimizador do BigQuery pode usá-las para processar consultas às tabelas base.
Use o BI Engine
Prática recomendada: use o BigQuery BI Engine para acelerar as consultas ao colocar em cache os dados que usa com maior frequência.
Considere adicionar uma reserva do BI Engine
ao projeto onde as consultas estão a ser calculadas.
O BigQuery BI Engine usa um motor de consultas vetorizado para acelerar o SELECT
desempenho das consultas.
Use índices de pesquisa
Prática recomendada: use índices de pesquisa para pesquisas de linhas eficientes quando precisar de encontrar linhas de dados individuais em tabelas grandes.
Um índice de pesquisa é uma estrutura de dados concebida para permitir uma pesquisa muito eficiente com a função SEARCH
, mas também pode acelerar as consultas que usam outros operadores e funções, como os operadores igual (=
), IN
ou LIKE
e determinadas funções de string e JSON.
Otimize as operações de consulta
Pode otimizar as operações de consulta através das opções descritas nas secções seguintes.
Evite transformar dados repetidamente
Prática recomendada: se estiver a usar SQL para realizar operações de ETL, evite situações em que transforma repetidamente os mesmos dados.
Por exemplo, se estiver a usar SQL para cortar strings ou extrair dados através de expressões regulares, é mais eficiente materializar os resultados transformados numa tabela de destino. As funções como as expressões regulares requerem cálculos adicionais. Consultar a tabela de destino sem a sobrecarga de transformação adicionada é muito mais eficiente.
Evite várias avaliações dos mesmos CTEs
Prática recomendada: use linguagem processual, variáveis, tabelas temporárias e tabelas com expiração automática para manter os cálculos e usá-los mais tarde na consulta.
Quando a sua consulta contém expressões de tabelas comuns (CTEs) que são usadas em vários locais na consulta, podem acabar por ser avaliadas sempre que são referenciadas. O otimizador de consultas tenta detetar partes da consulta que podem ser executadas apenas uma vez, mas isto pode nem sempre ser possível. Como resultado, a utilização de uma CTE pode não ajudar a reduzir a complexidade das consultas internas e o consumo de recursos.
Pode armazenar o resultado de uma CTE numa variável escalar ou numa tabela temporária, consoante os dados que a CTE devolve.
Evite uniões e subconsultas repetidas
Prática recomendada: evite juntar repetidamente as mesmas tabelas e usar as mesmas subconsultas.
Em vez de juntar os dados repetidamente, pode ser mais eficiente usar dados repetidos aninhados para representar as relações. Os dados repetidos aninhados evitam o impacto no desempenho da largura de banda de comunicação que uma junção requer. Também lhe permite poupar os custos de E/S que incorre ao ler e escrever repetidamente os mesmos dados. Para mais informações, consulte o artigo Use campos aninhados e repetidos.
Da mesma forma, a repetição das mesmas subconsultas afeta o desempenho através do processamento repetitivo de consultas. Se estiver a usar as mesmas subconsultas em várias consultas, considere materializar os resultados das subconsultas numa tabela. Em seguida, consuma os dados materializados nas suas consultas.
A materialização dos resultados da subconsulta melhora o desempenho e reduz a quantidade geral de dados que o BigQuery lê e escreve. O pequeno custo de armazenar os dados materializados compensa o impacto no desempenho da E/S repetida e do processamento de consultas.
Otimize os padrões de junção
Prática recomendada: para consultas que juntam dados de várias tabelas, otimize os padrões de junção começando pela tabela maior.
Quando cria uma consulta com uma cláusula JOIN
, considere a ordem em que está a
unir os dados. O otimizador de consultas GoogleSQL determina que tabela deve estar em que lado da junção. Como prática recomendada, coloque primeiro a tabela com o maior número de linhas, seguida da tabela com o menor número de linhas e, em seguida, coloque as tabelas restantes por ordem decrescente do tamanho.
Quando tem uma tabela grande no lado esquerdo do JOIN
e uma pequena no lado direito do JOIN
, é criada uma junção de transmissão. Uma junção de transmissão
envia todos os dados na tabela mais pequena para cada ranhura que processa a tabela
maior. É aconselhável participar primeiro na transmissão.
Para ver o tamanho das tabelas no seu JOIN
, consulte
Obtenha informações sobre tabelas.
Especifique restrições de chaves principais e chaves externas
Prática recomendada: especifique restrições de chaves no esquema da tabela quando os dados da tabela satisfazem os requisitos de integridade de dados das restrições de chaves primárias ou chaves externas. O motor de consultas pode usar as restrições de chaves para otimizar os planos de consultas.
O BigQuery não verifica automaticamente a integridade dos dados, pelo que tem de garantir que os seus dados cumprem as restrições especificadas no esquema da tabela. Se não mantiver a integridade dos dados nas tabelas com restrições especificadas, os resultados da consulta podem ser imprecisos.
Otimize a cláusula ORDER BY
Prática recomendada: quando usar a cláusula ORDER BY
, certifique-se de que segue as práticas recomendadas:
Use
ORDER BY
na consulta mais externa ou em cláusulas de janela. Envie operações complexas para o final da consulta. A colocação de uma cláusulaORDER BY
no meio de uma consulta afeta significativamente o desempenho, a menos que esteja a ser usada numa função de janela.Outra técnica para ordenar a sua consulta é enviar operações complexas, como expressões regulares e funções matemáticas, para o final da consulta. Esta técnica reduz os dados a serem processados antes de as operações complexas serem realizadas.
Use uma cláusula
LIMIT
. Se estiver a ordenar um número muito grande de valores, mas não precisar de que todos sejam devolvidos, use uma cláusulaLIMIT
. Por exemplo, a consulta seguinte ordena um conjunto de resultados muito grande e gera um erroResources exceeded
. A consulta ordena pela colunatitle
emmytable
. A colunatitle
contém milhões de valores.SELECT title FROM `my-project.mydataset.mytable` ORDER BY title;
Para remover o erro, use uma consulta como a seguinte:
SELECT title FROM `my-project.mydataset.mytable` ORDER BY title DESC LIMIT 1000;
Use uma função de janela. Se estiver a ordenar um número muito grande de valores, use uma função de janela e limite os dados antes de chamar a função de janela. Por exemplo, a seguinte consulta lista os dez utilizadores mais antigos do Stack Overflow e a respetiva classificação, sendo que a conta mais antiga é classificada como a mais baixa:
SELECT id, reputation, creation_date, DENSE_RANK() OVER (ORDER BY creation_date) AS user_rank FROM bigquery-public-data.stackoverflow.users ORDER BY user_rank ASC LIMIT 10;
Esta consulta demora aproximadamente 15 segundos a ser executada. Esta consulta usa
LIMIT
no final da consulta, mas não na função de janelaDENSE_RANK() OVER
. Por este motivo, a consulta requer que todos os dados sejam ordenados num único nó de trabalho.Em alternativa, deve limitar o conjunto de dados antes de calcular a função de janela para melhorar o desempenho:
WITH users AS ( SELECT id, reputation, creation_date, FROM bigquery-public-data.stackoverflow.users ORDER BY creation_date ASC LIMIT 10) SELECT id, reputation, creation_date, DENSE_RANK() OVER (ORDER BY creation_date) AS user_rank FROM users ORDER BY user_rank;
Esta consulta demora aproximadamente 2 segundos a ser executada, ao mesmo tempo que devolve os mesmos resultados que a consulta anterior.
Uma ressalva é que a função
DENSE_RANK()
classifica os dados ao longo dos anos, pelo que, para classificar dados que abrangem vários anos, estas consultas não dão resultados idênticos.
Divida consultas complexas em consultas mais pequenas
Prática recomendada: tire partido das capacidades de consultas com várias declarações e dos procedimentos armazenados para realizar os cálculos concebidos como uma consulta complexa em vez de várias consultas mais pequenas e simples.
As consultas complexas, as funções REGEX
e as subconsultas ou as junções em camadas podem ser lentas e exigir muitos recursos para serem executadas. Tentar ajustar todos os cálculos numa enorme declaração SELECT
, por exemplo, para a tornar numa vista, é, por vezes, um antipadrão e pode resultar numa consulta lenta e com utilização intensiva de recursos. Em casos extremos, o plano de consulta interno torna-se tão complexo que o BigQuery não consegue executá-lo.
Dividir uma consulta complexa permite materializar resultados intermédios em variáveis ou tabelas temporárias. Em seguida, pode usar estes resultados intermédios noutras partes da consulta. É cada vez mais útil quando estes resultados são necessários em mais do que um local da consulta.
Muitas vezes, permite-lhe expressar melhor a verdadeira intenção de partes da consulta, sendo as tabelas temporárias os pontos de materialização de dados.
Use campos aninhados e repetidos
Para obter informações sobre como desnormalizar o armazenamento de dados através de campos aninhados e repetidos, consulte o artigo Use campos aninhados e repetidos.
Use tipos de dados INT64
em associações
Prática recomendada: use tipos de dados INT64
em junções em vez de tipos de dados STRING
para reduzir o custo e melhorar o desempenho da comparação.
O BigQuery não indexa chaves primárias como as bases de dados tradicionais. Por isso, quanto mais ampla for a coluna de união, mais tempo demora a comparação. Por conseguinte, os tipos de dados INT64
nas junções são mais baratos e eficientes do que os tipos de dados STRING
.
Reduza os resultados das consultas
Pode reduzir os resultados da consulta através das opções descritas nas secções seguintes.
Materialize conjuntos de resultados grandes
Prática recomendada: considere materializar grandes conjuntos de resultados numa tabela de destino. A gravação de grandes conjuntos de resultados tem impactos no desempenho e nos custos.
O BigQuery limita os resultados em cache a aproximadamente 10 GB comprimidos. As consultas que devolvem resultados maiores ultrapassam este limite e, frequentemente, resultam no seguinte erro: Response too large
.
Este erro ocorre frequentemente quando seleciona um grande número de campos de uma tabela com uma quantidade considerável de dados. Também podem ocorrer problemas na escrita de resultados em cache em consultas no estilo ETL que normalizam os dados sem redução nem agregação.
Pode ultrapassar a limitação do tamanho dos resultados em cache através das seguintes opções:
- Utilize filtros para limitar o conjunto de resultados
- Use uma cláusula
LIMIT
para reduzir o conjunto de resultados, especialmente se estiver a usar uma cláusulaORDER BY
- Escrever os dados de saída numa tabela de destino
Pode paginar os resultados através da API REST do BigQuery. Para mais informações, consulte o artigo Paginação de dados de tabelas.
Evite padrões anti-SQL
As seguintes práticas recomendadas fornecem orientações sobre como evitar padrões de consultas que afetam o desempenho no BigQuery.
Evite associações próprias
Prática recomendada: em vez de usar junções automáticas, use uma
função de janela (analítica)
ou o
operador PIVOT
.
Normalmente, as junções automáticas são usadas para calcular relações dependentes das linhas. O resultado da utilização de uma junção automática é que potencialmente eleva ao quadrado o número de linhas de saída. Este aumento nos dados de saída pode causar um desempenho fraco.
Evite cruzamentos de uniões
Prática recomendada: evite junções que gerem mais resultados do que entradas. Quando é necessário um
CROSS JOIN
, pré-agregue os seus dados.
As junções cruzadas são consultas em que cada linha da primeira tabela é unida a todas as linhas da segunda tabela, com chaves não únicas em ambos os lados. O pior resultado é o número de linhas na tabela à esquerda multiplicado pelo número de linhas na tabela à direita. Em casos extremos, a consulta pode não terminar.
Se a tarefa de consulta for concluída, a explicação do plano de consulta mostra as linhas de saída
em comparação com as linhas de entrada. Pode confirmar um produto cartesiano
modificando a consulta para imprimir o número de linhas de cada lado da cláusula JOIN
, agrupadas pela chave de junção. Também pode verificar as estatísticas de desempenho no gráfico de execução de consultas para uma junção de cardinalidade elevada.
Para evitar problemas de desempenho associados a uniões que geram mais saídas do que entradas:
- Use uma cláusula
GROUP BY
para agregar previamente os dados. - Use uma função de janela. As funções de janela são muitas vezes mais eficientes do que usar uma união cruzada. Para mais informações, consulte o artigo sobre funções de janela.
Evite declarações DML que atualizam ou inserem linhas únicas
Prática recomendada: evite declarações DML que atualizam ou inserem linhas únicas. Agrupe as atualizações e as inserções.
A utilização de declarações DML específicas de pontos é uma tentativa de tratar o BigQuery como um sistema de processamento de transações online (OLTP). O BigQuery foca-se no processamento analítico online (OLAP) através de análises de tabelas e não de pesquisas de pontos. Se precisar de um comportamento semelhante ao OLTP (atualizações ou inserções de uma única linha), considere uma base de dados concebida para suportar exemplos de utilização de OLTP, como o Cloud SQL.
As declarações DML do BigQuery destinam-se a atualizações em massa. UPDATE
e as declarações DML no BigQuery estão orientadas para
reescritas periódicas dos seus dados e não para mutações de linhas únicas.DELETE
A declaração INSERT
DML
destina-se a ser usada com moderação. As inserções consomem as mesmas quotas de modificação que as tarefas de carregamento. Se o seu exemplo de utilização envolver inserções frequentes de linhas únicas, considere fazer o streaming dos seus dados.
Se o processamento em lote das suas declarações UPDATE
gerar muitas tuplas em consultas muito longas, pode aproximar-se do limite de comprimento da consulta de 256 KB. Para contornar o limite de comprimento da consulta, pondere se as suas atualizações podem ser processadas com base num critério lógico em vez de uma série de substituições diretas de tuplos.
Por exemplo, pode carregar o conjunto de registos de substituição noutra tabela e, em seguida, escrever a declaração DML para atualizar todos os valores na tabela original se as colunas não atualizadas corresponderem. Por exemplo, se os dados originais estiverem na tabela t
e as atualizações forem preparadas na tabela u
, a consulta teria o seguinte aspeto:
UPDATE dataset.t t SET my_column = u.my_column FROM dataset.u u WHERE t.my_key = u.my_key
Use nomes de alias para colunas com nomes semelhantes
Prática recomendada: use alias de colunas e tabelas quando trabalhar com colunas com nomes semelhantes em várias consultas, incluindo subconsultas.
Os alias ajudam a identificar as colunas e as tabelas referenciadas, além da referência inicial da coluna. A utilização de aliases pode ajudar a compreender e resolver problemas na sua consulta SQL, incluindo encontrar as colunas que são usadas em subconsultas.
O que se segue?
- Saiba como otimizar o custo.
- Saiba como otimizar o armazenamento.
- Saiba como otimizar as funções.