Processamento de consultas sem a otimização PREWHERE
① A consulta inclui um filtro na coluna
town, que faz parte da chave primária da tabela e, portanto, também do índice primário.
② Para acelerar a consulta, o ClickHouse carrega o índice primário da tabela na memória.
③ Ele percorre as entradas do índice para identificar quais grânulos da coluna town podem conter linhas que correspondam ao predicado.
④ Esses grânulos potencialmente relevantes são carregados na memória, junto com grânulos alinhados por posição de quaisquer outras colunas necessárias para a consulta.
⑤ Os filtros restantes são então aplicados durante a execução da consulta.
Como você pode ver, sem PREWHERE, todas as colunas potencialmente relevantes são carregadas antes da filtragem, mesmo que apenas algumas linhas realmente correspondam.
Como o PREWHERE melhora a eficiência da consulta
① A consulta inclui um filtro na coluna
town, que faz parte da chave primária da tabela — e, portanto, também do índice primário.
② Assim como na execução sem a cláusula PREWHERE, para acelerar a consulta, o ClickHouse carrega o índice primário na memória
③ e, em seguida, percorre as entradas do índice para identificar quais grânulos da coluna town podem conter linhas que correspondam ao predicado.
Agora, graças à cláusula PREWHERE, a próxima etapa é diferente: em vez de ler todas as colunas relevantes logo no início, o ClickHouse filtra os dados coluna por coluna, carregando apenas o que realmente é necessário. Isso reduz drasticamente a E/S, especialmente em tabelas largas.
A cada etapa, ele carrega apenas os grânulos que contêm pelo menos uma linha que passou — isto é, correspondeu — ao filtro anterior. Como resultado, o número de grânulos a carregar e avaliar em cada filtro diminui monotonicamente:
Etapa 1: Filtragem por townO ClickHouse inicia o processamento do PREWHERE ① lendo os grânulos selecionados da coluna
town e verificando quais deles realmente contêm linhas que correspondem a London.
No nosso exemplo, todos os grânulos selecionados correspondem, então ② os grânulos correspondentes, alinhados posicionalmente, da próxima coluna de filtro — date — são selecionados para processamento:
Etapa 2: Filtragem por date
Em seguida, o ClickHouse ① lê os grânulos selecionados da coluna
date para avaliar o filtro date > '2024-12-31'.
Nesse caso, dois dos três grânulos contêm linhas correspondentes, então ② apenas os grânulos correspondentes, alinhados posicionalmente, da próxima coluna de filtro — price — são selecionados para processamento adicional:
Etapa 3: Filtragem por price
Por fim, o ClickHouse ① lê os dois grânulos selecionados da coluna
price para avaliar o último filtro price > 10_000.
Apenas um dos dois grânulos contém linhas correspondentes, então ② somente o grânulo correspondente, alinhado posicionalmente, da coluna do SELECT — street — precisa ser carregado para processamento adicional:
Na etapa final, apenas o conjunto mínimo de grânulos de coluna — aqueles que contêm linhas correspondentes — é carregado. Isso resulta em menor uso de memória, menos E/S de disco e execução mais rápida da consulta.
O PREWHERE reduz os dados lidos, não o número de linhas processadasObserve que o ClickHouse processa o mesmo número de linhas tanto na versão da consulta com PREWHERE quanto na versão sem PREWHERE. No entanto, com as otimizações de PREWHERE aplicadas, nem todos os valores das colunas precisam ser carregados para cada linha processada.
A otimização PREWHERE é aplicada automaticamente
optimize_move_to_prewhere está habilitada (true por padrão), o ClickHouse move automaticamente as condições de filtro de WHERE para PREWHERE, priorizando aquelas que mais reduzem o volume de leitura.
A ideia é que colunas menores sejam mais rápidas de ler e, quando as colunas maiores forem processadas, a maioria dos grânulos já tenha sido filtrada. Como todas as colunas têm o mesmo número de linhas, o tamanho de uma coluna é determinado principalmente pelo seu tipo de dado; por exemplo, uma coluna UInt8 geralmente é muito menor do que uma coluna String.
O ClickHouse segue essa estratégia por padrão desde a versão 23.2, ordenando as colunas de filtro em PREWHERE para processamento em várias etapas em ordem crescente de tamanho não comprimido.
A partir da versão 23.11, estatísticas opcionais de coluna podem aprimorar ainda mais esse processo, escolhendo a ordem de processamento dos filtros com base na seletividade real dos dados, e não apenas no tamanho da coluna.
Como medir o impacto do PREWHERE
optimize_move_to_prewhere ativada.
Começamos executando a consulta com a configuração optimize_move_to_prewhere desativada:
optimize_move_to_prewhere habilitada. (Observe que essa configuração é opcional, pois já é habilitada por padrão):
Principais pontos
- PREWHERE evita ler dados de colunas que depois seriam filtrados, economizando E/S e memória.
- Ele funciona automaticamente quando
optimize_move_to_prewhereestá habilitado (padrão). - A ordem dos filtros importa: colunas pequenas e seletivas devem vir primeiro.
- Use
EXPLAINe os logs para verificar se PREWHERE está sendo aplicado e entender seu efeito. - PREWHERE tem maior impacto em tabelas largas e varreduras grandes com filtros seletivos.