O PolarDB-X traduz cada instrução SQL em uma árvore de operadores antes da execução. Execute EXPLAIN em qualquer consulta para visualizar quais operadores foram escolhidos e o motivo. Esta referência descreve a função de cada operador, o significado de seus argumentos e quando o otimizador os seleciona.
Referência de operadores
|
Categoria |
Operadores |
|
Push down para nós de dados |
|
|
Join |
|
|
Ordenação |
|
|
Agregação (GROUP BY) |
|
|
Redistribuição ou coleta de dados |
|
|
Filtragem de linhas |
|
|
Seleção de colunas |
|
|
Mesclagem de conjuntos de resultados |
|
|
Limitação de linhas de saída |
|
|
Função de janela |
|
Locais de execução
Os operadores do PolarDB-X são executados em um destes dois locais:
Nós de dados (camada de armazenamento): Os operadores enviados via push down são executados diretamente nos shards de armazenamento, próximos aos dados. Essa estratégia reduz o volume de dados transferidos para o nó de computação e melhora o desempenho da consulta.
Nós de computação: Operadores que não podem ser enviados via push down — geralmente por dependerem de resultados de múltiplos shards — são executados no nó de computação após a camada de armazenamento retornar resultados parciais.
O objetivo do otimizador é delegar o máximo possível de trabalho à camada de armazenamento. Os operadores de push down (LogicalView, LogicalModifyView, PhyTableOperation, IndexScan) representam esse trabalho delegado. Todos os demais operadores são executados no nó de computação.
Operadores com push down para nós de dados
LogicalView
O operador LogicalView lê dados dos nós de dados. Trata-se do principal operador de push down para instruções SELECT, capaz de delegar um conjunto mais amplo de operações do que os operadores TableScan e IndexScan — incluindo Project, Filter, operadores de agregação, ordenação, join e subconsultas.
Um plano de execução sempre exibe o modelo SQL executado na camada de armazenamento, juntamente com a lista de shards de tabela alvo.
Quando o otimizador o utiliza: Sempre que uma instrução SELECT tem como alvo um ou mais shards, o LogicalView aparece no plano.
Exemplo
EXPLAIN SELECT * FROM sbtest1 WHERE id > 1000;
Saída:
Gather(concurrent=true)
LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT * FROM `sbtest1` WHERE (`id` > ?)")
Argumentos
|
Argumento |
Descrição |
|
|
Shards de tabela alvo da instrução. Formato: |
|
|
Quantidade total de shards de tabela verificados. No exemplo, 128 shards são escaneados. |
|
|
Modelo SQL enviado à camada de armazenamento. O PolarDB-X substitui o nome da tabela pelo nome da tabela física durante a execução e preenche os placeholders |
LogicalModifyView
O LogicalModifyView grava dados nos nós de dados. Ele abrange instruções INSERT, UPDATE e DELETE, contendo os mesmos campos do LogicalView: nomes dos shards de tabela física, contagem de shards e um modelo SQL.
Quando o cache de plano de execução está ativado, as constantes no modelo SQL são substituídas por placeholders ?.
Quando o otimizador o utiliza: Sempre que uma instrução de escrita (INSERT, UPDATE ou DELETE) tem como alvo um ou mais shards, o LogicalModifyView aparece no plano.
Exemplos
EXPLAIN UPDATE sbtest1 SET c='Hello, DRDS' WHERE id > 1000;
Saída:
LogicalModifyView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="UPDATE `sbtest1` SET `c` = ? WHERE (`id` > ?)")
EXPLAIN DELETE FROM sbtest1 WHERE id > 1000;
Saída:
LogicalModifyView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="DELETE FROM `sbtest1` WHERE (`id` > ?)")
PhyTableOperation
O PhyTableOperation opera diretamente em um único shard de tabela física. Seu uso ocorre principalmente em instruções INSERT. Quando um SELECT é roteado para um shard de tabela, o PhyTableOperation o executa.
Em um INSERT com múltiplas linhas, cada linha recebe seu próprio PhyTableOperation.
Quando o otimizador o utiliza: Em instruções INSERT — cada linha é atribuída a exatamente um shard físico. Também é usado quando uma instrução SELECT é roteada para um shard de tabela.
Exemplo
EXPLAIN INSERT INTO sbtest1 VALUES(1, 1, '1', '1'),(2, 2, '2', '2');
Saída:
PhyTableOperation(tables="SYSBENCH_CORONADB_1526954857179TGMMSYSBENCH_CORONADB_VGOC_0000_RDS.[sbtest1_001]", sql="INSERT INTO ? (`id`, `k`, `c`, `pad`) VALUES(?, ?, ?, ?)", params="`sbtest1_001`,1,1,1,1")
PhyTableOperation(tables="SYSBENCH_CORONADB_1526954857179TGMMSYSBENCH_CORONADB_VGOC_0000_RDS.[sbtest1_002]", sql="INSERT INTO ? (`id`, `k`, `c`, `pad`) VALUES(?, ?, ?, ?)", params="`sbtest1_002`,2,2,2,2")
Duas linhas no INSERT geram duas entradas PhyTableOperation, uma por shard.
Argumentos
|
Argumento |
Descrição |
|
|
Nome da tabela física para esta operação. Cada |
|
|
Modelo SQL com o nome da tabela e constantes substituídos por placeholders |
|
|
Valores que preenchem os placeholders |
IndexScan
O IndexScan lê dados dos nós de dados usando um índice secundário global (GSI) em vez da tabela base. Seu comportamento é idêntico ao do LogicalView, exceto pelo fato de escanear uma tabela de índice em vez de uma tabela base.
Quando o otimizador o utiliza: Se um predicado de consulta corresponder à chave de partição de uma coluna GSI, o otimizador usa o IndexScan para atingir apenas o shard de índice relevante, evitando uma varredura completa na tabela base. Caso não exista GSI na coluna do predicado, ou se a coluna do predicado não for uma chave de partição, o LogicalView escaneia todos os shards da tabela base.
Exemplo
EXPLAIN SELECT * FROM sequence_one_base WHERE integer_test=1;
Saída:
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
IndexScan(tables="DRDS_POLARX1_QATEST_APP_000000_GROUP.gsi_sequence_one_index_3a0A_01", sql="SELECT `pk`, `integer_test`, `varchar_test`, `char_test`, `blob_test`, `tinyint_test`, `tinyint_1bit_test`, `smallint_test`, `mediumint_test`, `bit_test`, `bigint_test`, `float_test`, `double_test`, `decimal_test`, `date_test`, `time_test`, `datetime_test`, `timestamp_test`, `year_test`, `mediumtext_test` FROM `gsi_dml_sequence_one_index_index1` AS `gsi_dml_sequence_one_index_index1` WHERE (`integer_test` = ?)")
Neste exemplo, sequence_one_base possui um GSI chamado gsi_sequence_one_index na coluna integer_test. Como integer_test=1 corresponde à chave de partição do GSI, apenas um shard de índice é escaneado. Sem o GSI, ou se integer_test não fosse uma chave de partição, todos os shards da tabela base seriam escaneados.
Operadores que coletam ou redistribuem dados
Estes operadores são executados no nó de computação e gerenciam a movimentação de dados entre os shards e o nó de computação.
Gather
O Gather mescla resultados de múltiplos shards de tabela em um único conjunto de resultados. Ele aparece como pai do LogicalView na maioria dos planos de execução, coletando dados de todos os shards escaneados.
Quando o otimizador o utiliza: Sempre que o LogicalView escaneia múltiplos shards e o nó de computação precisa de um conjunto de resultados unificado, o Gather aparece acima do LogicalView na árvore do plano.
Exchange
O Exchange é um operador lógico que redistribui dados entre nós sem realizar computação. Ele alimenta operadores subsequentes com dados redistribuídos. Três estratégias de redistribuição são utilizadas:
|
Estratégia |
Comportamento |
Uso típico |
|
|
Mescla múltiplos fluxos de dados em um só |
Equivalente ao |
|
|
Reparticiona linhas por valor de hash nas colunas especificadas |
Planos de execução de join e agregação |
|
|
Transmite uma cópia dos dados para cada nó subsequente |
Planos de execução de processamento massivamente paralelo (MPP) |
Quando o otimizador o utiliza: O Exchange aparece em planos de execução onde os dados devem ser redistribuídos entre nós antes que um join ou agregação possa prosseguir. A estratégia de redistribuição escolhida depende do operador subsequente: distribuição por hash para joins e agregações, broadcast para planos de execução MPP e singleton quando um único fluxo é necessário.
MergeSort
O MergeSort combina múltiplos fluxos de dados ordenados de diferentes shards em um único fluxo ordenado. O otimizador o utiliza quando uma consulta com ORDER BY abrange múltiplos shards — cada shard ordena localmente e o MergeSort mescla os resultados no nó de computação.
Quando o otimizador o utiliza: Se uma cláusula ORDER BY não puder ser atendida por um único shard, cada shard ordena suas linhas localmente (visível como ORDER BY no modelo SQL do LogicalView) e o MergeSort realiza a mesclagem final no nó de computação. Frequentemente aparece junto com LIMIT para consultas top-N eficientes.
Exemplo
EXPLAIN SELECT * FROM sbtest1 WHERE id > 1000 ORDER BY id LIMIT 5,10;
Saída:
MergeSort(sort="id ASC", offset=?1, fetch=?2)
LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT * FROM `sbtest1` WHERE (`id` > ?) ORDER BY `id` LIMIT (? + ?)")
Argumentos
|
Argumento |
Descrição |
|
|
Coluna e direção usadas para ordenação. |
|
|
Número de linhas a ignorar após a ordenação. O valor é parametrizado; o valor real no exemplo é |
|
|
Número máximo de linhas a retornar. O valor é parametrizado; o valor real no exemplo é |
UnionAll e UnionDistinct
Os operadores UnionAll e UnionDistinct mesclam dois ou mais conjuntos de resultados em um só. O UnionAll corresponde ao UNION ALL; já o UnionDistinct corresponde ao UNION DISTINCT e remove linhas duplicadas. Esses operadores podem ser executados tanto em nós de computação quanto em nós de dados, dependendo da decisão do otimizador.
Exemplo
EXPLAIN SELECT * FROM sbtest1 WHERE id > 1000
UNION DISTINCT
SELECT * FROM sbtest1 WHERE id < 200;
Saída:
UnionDistinct(concurrent=true)
Gather(concurrent=true)
LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT * FROM `sbtest1` WHERE (`id` > ?)")
Gather(concurrent=true)
LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT * FROM `sbtest1` WHERE (`id` < ?)")
Operadores para seleção de colunas e filtragem de linhas
Project
O Project seleciona colunas das linhas de entrada, avalia expressões e gera o resultado. Ele pode calcular expressões aritméticas, chamar funções ou retornar constantes.
Quando o otimizador o utiliza: O Project aparece sempre que as colunas de saída diferem das colunas de entrada — por exemplo, quando uma consulta seleciona colunas específicas, calcula valores derivados ou aplica funções. Predicados que podem ser avaliados na camada de armazenamento são enviados via push down para o LogicalView.
Exemplo
EXPLAIN SELECT 'Hello, DRDS', 1 / 2, CURTIME();
Saída:
Project(Hello, DRDS="_UTF-16'Hello, DRDS'", 1 / 2="1 / 2", CURTIME()="CURTIME()")
O plano lista cada coluna de saída ao lado de seu valor de origem, expressão ou função.
Filter
O Filter avalia um predicado e transmite apenas as linhas que o satisfazem. A condição mostrada no plano é o predicado aplicado no nó de computação. Predicados passíveis de avaliação na camada de armazenamento são enviados via push down para o LogicalView.
Quando o otimizador o utiliza: O Filter surge quando um predicado não pode ser enviado à camada de armazenamento — tipicamente quando depende de um valor agregado ou calculado (como uma cláusula HAVING), ou quando o predicado referencia colunas disponíveis somente após um join ou agregação no nó de computação.
Exemplo
EXPLAIN SELECT k, AVG(id) avg_id FROM sbtest1 WHERE id > 1000 GROUP BY k HAVING avg_id > 1300;
Saída:
Filter(condition="avg_id > ?1")
Project(k="k", avg_id="sum_pushed_sum / sum_pushed_count")
SortAgg(group="k", sum_pushed_sum="SUM(pushed_sum)", sum_pushed_count="SUM(pushed_count)")
MergeSort(sort="k ASC")
LogicalView(tables="[0000-0031].sbtest1_[000-127]", shardCount=128, sql="SELECT `k`, SUM(`id`) AS `pushed_sum`, COUNT(`id`) AS `pushed_count` FROM `sbtest1` WHERE (`id` > ?) GROUP BY `k` ORDER BY `k`")
A condição WHERE id > 1000 não aparece no Filter porque foi enviada via push down para o LogicalView — visível como WHERE (id > ?) no modelo SQL. Apenas a condição HAVING avg_id > 1300, que depende do resultado agregado, permanece no Filter.