Todos os produtos
Search
Central de documentação

MaxCompute:Otimização de consultas SQL

Última atualização: Jun 26, 2026

Se uma consulta SQL no MaxCompute apresentar lentidão, a causa raiz frequentemente não é a lógica do SQL, mas a distribuição da carga de trabalho. Um grau de paralelismo (DOP) mal configurado — ou seja, o número de instâncias paralelas que executam o job — pode criar gargalos graves de desempenho. Essa configuração inadequada pode forçar todos os dados para um único worker, prejudicando severamente a performance, ou gerar sobrecarga excessiva ao iniciar muitas instâncias para uma tarefa pequena, resultando em longos tempos de fila. Este guia apresenta técnicas práticas para diagnosticar e ajustar o DOP conforme sua carga de trabalho específica. Alinhar corretamente o DOP com seus dados e a estrutura da consulta melhora drasticamente a velocidade de execução e otimiza a utilização de recursos.

Otimização do grau de paralelismo (DOP)

O grau de paralelismo (DOP) mede quantas instâncias paralelas executam um job. Por exemplo, se uma tarefa com o ID M1 utiliza 1.000 instâncias, o DOP de M1 é 1000. Configurar adequadamente o DOP aumenta significativamente a eficiência na execução de jobs.

As seções a seguir descrevem cenários comuns de otimização de DOP.

Forçar execução em instância única

Certas operações forçam a execução de um job em uma única instância, eliminando todo o paralelismo e criando um gargalo severo. Essas operações incluem:

  • Agregação sem a cláusula GROUP BY ou com uma cláusula GROUP BY baseada em constante.

  • Uso de função de janela em que a cláusula OVER especifica PARTITION BY como constante. Omitir totalmente o PARTITION BY produz o mesmo efeito: todos os dados são ordenados e agregados em uma única instância.

  • Uso das cláusulas DISTRIBUTE BY ou CLUSTER BY sobre uma constante.

Para evitar esse gargalo, avalie se uma agregação global é realmente necessária. Em caso afirmativo, adote um padrão de agregação em dois estágios: primeiro agrupe por uma chave de alta cardinalidade para executar uma agregação parcial em todas as instâncias paralelamente; em seguida, realize a agregação final sobre o conjunto de resultados intermediários, que será muito menor.

Impacto da contagem incorreta de instâncias

Importante

Maior paralelismo nem sempre resulta em melhor desempenho. Utilizar instâncias em excesso pode retardar a execução por dois motivos:

  • Paralelismo excessivo aumenta a contenção de recursos e os tempos de espera em fila.

  • Cada instância possui uma fase de inicialização. Com um DOP elevado, a sobrecarga acumulada de inicialização reduz o tempo disponível para computação efetiva.

Os cenários abaixo costumam gerar uma contagem de instâncias subótima:

  • Leitura de um grande número de partições pequenas: Se uma consulta varre 10.000 partições, o sistema pode iniciar 10.000 instâncias. Cada instância termina em milissegundos, mas passa a maior parte desse tempo aguardando na fila.

    Reduza a contagem de varredura de partições aplicando uma poda de partições eficaz no início da consulta, filtrando partições desnecessárias ou dividindo a consulta em jobs menores e mais direcionados.

  • Tamanho de divisão pequeno para mappers: O tamanho padrão de divisão de 256 MB fragmenta grandes conjuntos de dados de entrada em muitas instâncias pequenas. Cada instância executa por pouco tempo, sendo que a maior parte dele é consumida pelo enfileiramento de recursos, e não pela computação.

    Aumente o tamanho da divisão para que cada instância mapper processe mais dados. Também é possível definir explicitamente a contagem de instâncias reducer.

    SET odps.stage.mapper.split.size=<256>;
    SET odps.stage.reducer.num=<Maximum number of concurrent instances>;

Configurar o número de instâncias

  • Para tarefas de leitura de tabela (mappers)

    • Método 1: Definir um parâmetro global de tamanho de divisão.

      -- Configure the maximum amount of input data per mapper instance. Unit: MB.
      -- Default value: 256. Valid values: [1,Integer.MAX_VALUE].
      SET odps.sql.mapper.split.size=<value>;
    • Método 2: Usar uma dica de consulta para controle por tabela. A dica split_size substitui o parâmetro global para uma operação específica de leitura de tabela, oferecendo controle mais granular sem afetar o restante da consulta.

      -- Split the src table into subtasks of 1 MB each.
      SELECT a.key FROM src a /*+split_size(1)*/ JOIN src2 b ON a.key=b.key;
    • Método 3: Dividir dados no nível da tabela por tamanho, contagem de linhas ou um DOP especificado.

    O parâmetro odps.sql.mapper.split.size aplica-se globalmente a todos os estágios mapper e tem valor mínimo de 1 MB. Para casos em que as linhas são pequenas, mas computacionalmente custosas — situações em que se deseja mais instâncias sem alterar o volume de dados por divisão — utilize os seguintes parâmetros no nível da tabela.

    Use os parâmetros abaixo para ajuste de DOP no nível da tabela:

    • Defina o tamanho da divisão de dados por instância no nível da tabela.

      SET odps.sql.split.size = {"table1": 1024, "table2": 512};
    • Defina a contagem de linhas processadas por instância no nível da tabela.

      SET odps.sql.split.row.count = {"table1": 100, "table2": 500};
    • Defina o DOP diretamente no nível da tabela.

      SET odps.sql.split.dop = {"table1": 1, "table2": 5};
    Nota

    Os parâmetros odps.sql.split.row.count e odps.sql.split.dop aplicam-se apenas a tabelas internas, tabelas não transacionais e tabelas não clusterizadas.

  • Para tarefas que não são de leitura (reducers e joiners)

    • Método 1: Definir a contagem de instâncias reducer. Esta configuração aplica-se a todas as tarefas reducer na consulta.

      -- Set the number of reducer instances.
      -- Valid values: [1,99999].
      SET odps.stage.reducer.num=<value>;
    • Método 2: Definir a contagem de instâncias joiner. Esta configuração aplica-se a todas as tarefas joiner na consulta.

      -- Set the number of joiner instances.
      -- Valid values: [1,99999].
      SET odps.stage.joiner.num=<value>;
    • Método 3: Ajustar a contagem de mappers anteriores. A contagem de instâncias reducer deriva do estágio mapper precedente. Aumentar a contagem de mappers eleva indiretamente o paralelismo dos reducers.

Otimização de funções de janela

Cada função de janela em uma consulta geralmente aciona um job reduce separado. Quando uma consulta contém múltiplas funções de janela, isso multiplica significativamente o consumo de recursos. O MaxCompute mescla automaticamente várias funções de janela em um único job reduce quando ambas as condições abaixo são atendidas:

  • As cláusulas OVER são idênticas — mesmas condições de PARTITION BY e ORDER BY.

  • As funções de janela aparecem na mesma instrução SELECT.

A consulta a seguir qualifica-se para mesclagem automática porque tanto RANK() quanto ROW_NUMBER() compartilham a mesma cláusula OVER e aparecem na mesma instrução SELECT. O MaxCompute as executa em um único job reduce em vez de dois.

SELECT
RANK() OVER (PARTITION BY A ORDER BY B desc) AS RANK,
ROW_NUMBER() OVER (PARTITION BY A ORDER BY B desc) AS row_num
FROM MyTable;

Otimização de subconsultas

Considere uma consulta que filtra usando uma subconsulta com IN:

SELECT * FROM table_a a WHERE a.col1 IN (SELECT col1 FROM table_b b WHERE xxx);

Se a subconsulta em table_b retornar mais de 9.999 valores para col1, o MaxCompute relata: records returned from subquery exceeded limit of 9999. Reescreva a consulta como um JOIN para remover esse limite:

SELECT a.* FROM table_a a JOIN (SELECT DISTINCT col1 FROM table_b b WHERE xxx) c ON (a.col1 = c.col1);
Nota
  • Omitir o DISTINCT pode fazer com que valores duplicados de col1 da subconsulta c multipliquem as linhas da tabela a, gerando mais resultados do que o esperado.

  • O uso de DISTINCT força a subconsulta para um único reducer, o que se torna um gargalo para grandes conjuntos de dados.

  • Se a lógica de negócio garantir que os valores de col1 sejam únicos, remova o DISTINCT para evitar o gargalo de reducer único.

Otimização de instruções JOIN

Para que o MaxCompute aplique poda de partições durante um JOIN, filtre as tabelas particionadas antes da execução da junção, e não depois. Sem essa filtragem antecipada, o sistema realiza o JOIN em todas as partições primeiro e só então aplica o filtro, varrendo muito mais dados do que o necessário.

Siga estas regras:

  • Aplique condições de limitação de partição na tabela principal dentro de uma subconsulta antes do JOIN.

  • Posicione outras cláusulas WHERE que filtram a tabela principal ao final da instrução SQL.

  • Aplique condições de limitação de partição na tabela secundária na cláusula ON ou em uma subconsulta — nunca na cláusula WHERE final.

Os exemplos a seguir ilustram essas práticas.

SELECT * FROM A JOIN (SELECT * FROM B WHERE dt=20150301)B ON B.id=A.id WHERE A.dt=20150301;
SELECT * FROM A JOIN B ON B.id=A.id WHERE B.dt=20150301; -- We recommend that you do not use this statement. The system performs the JOIN operation before it performs partition pruning. This increases the amount of data and causes the query performance to deteriorate. 
SELECT * FROM (SELECT * FROM A WHERE dt=20150301)A JOIN (SELECT * FROM B WHERE dt=20150301)B ON B.id=A.id;

Otimização de funções de agregação

Para agregação de strings, wm_concat geralmente supera collect_list em desempenho. Os exemplos a seguir mostram operações equivalentes usando cada função.

-- Implement the collect_list function.
SELECT concat_ws(',', sort_array(collect_list(key))) FROM src;
-- Implement the wm_concat function for better performance.
SELECT wm_concat(',', key) WITHIN GROUP (ORDER BY key) FROM src;

-- Implement the collect_list function.
SELECT array_join(collect_list(key), ',') FROM src;
-- Implement the wm_concat function for better performance.
SELECT wm_concat(',', key) FROM src;