Oracle Anti-Join Execution Plan Pergunta
-
20-09-2019 - |
Pergunta
Temos duas mesas como isso:
Event
id
type
... a bunch of other columns
ProcessedEvent
event_id
process
Existem índices definidos para
- Evento (ID) (PK)
- ProcessEdEvent (Event_id, processo)
O primeiro representa eventos em um aplicativo.
O segundo representa o fato de que um determinado evento obteve processos por um determinado processo. Existem muitos processos que precisam processar um determinado evento; portanto, existem várias entradas na segunda tabela para cada entrada no primeiro.
Para encontrar todos os eventos que precisam de processamento, executamos a seguinte consulta:
select * // of course we do name the columns in the production code
from Event
where type in ( 'typeA', 'typeB', 'typeC')
and id not in (
select event_id
from ProcessedEvent
where process = :1
)
As estatísticas estão atualizadas
Como a maioria dos eventos é processada, acho que o melhor plano de execução deve parecer algo assim
- Índice Completo de Índice no Índice ProcessEvent
- Índice Completo de Índice no Índice de Eventos
- anti -junção entre os dois
- Acesso à tabela com o resto
- filtro
Em vez disso, o Oracle faz o seguinte
- Índice Completo de Índice no Índice ProcessEvent
- Tabela completa digitalização na tabela de eventos
- filtre a tabela de eventos
- anti -junção entre os dois conjuntos
Com uma dica de índice, recebo o Oracle para fazer o seguinte:
- Índice Completo de Índice no Índice ProcessEvent
- Índice Completo de Índice no Índice de Eventos
- Tabela Acce na tabela de eventos
- filtre a tabela de eventos
- anti -junção entre os dois conjuntos
O que é realmente estúpido IMHO.
Portanto, minha pergunta é: qual pode ser o motivo do Oracle insistir no acesso à tabela inicial?
Adição: o desempenho é ruim. Estamos corrigindo o problema de desempenho selecionando apenas o evento. Mas é claro que isso é apenas um trabalho por aí.
Solução
Sua varredura completa do índice provavelmente será mais rápida que uma varredura completa, já que o índice provavelmente é "mais fino" que a tabela. Ainda assim, a verificação completa do índice é uma leitura completa do segmento e será o mesmo custo da varredura completa da tabela.
No entanto, você também está adicionando um acesso à tabela por etapa rowid. É um passo caro: um io lógico por linha Para o acesso a Rowid, enquanto você obtém um io lógico por multi blocos (dependendo do seu db_file_multiblock_read_count parameter
) para a varredura completa da tabela.
Em conclusão, o otimizador calcula que:
cost(FULL TABLE SCAN) < cost(FULL INDEX SCAN) + cost(TABLE ACCESS BY ROWID)
Atualizar: A varredura completa da tabela também permite o filtro no tipo mais cedo do que no caminho de varredura de índice completo (como o índice não sabe que tipo é um evento), reduzindo o tamanho do conjunto que será anti-jacuncional (ainda mais vantagem da varredura completa da tabela).
Outras dicas
O otimizador faz muitas coisas que não fazem sentido a princípio, mas têm suas razões. Eles nem sempre são certo, mas eles são compreensíveis.
A tabela de eventos pode ser mais fácil para variar em pleno e não pelo RowID Access devido ao seu tamanho. Pode ser que haja significativamente menos operações de IO envolvidas para ler toda a tabela sequencialmente do que ler bits e peças.
O desempenho é ruim, ou você está apenas perguntando por que o otimizador fez isso?
Não posso explicar o comportamento do otimizador, mas minha experiência tem sido evitar "não em todo custo", substituindo -o por menos, assim:
select * from Event
where id in (
select id from Event where type in ( 'typeA', 'typeB', 'typeC')
minus
select id from ProcessedEvent
)
Eu vi ordens de magnitude no desempenho da consulta com transformações semelhantes.
Algo como:
WITH
PROCEEDED AS
(
SELECT
event_id
FROM
ProcessedEvent
WHERE
PROCESS = :1
)
SELECT
* // of course we do name the columns in the production code
FROM
EVENT
LEFT JOIN PROCEEDED P
ON
p.event_id = EVENT.event_id
WHERE
type IN ( 'typeA', 'typeB', 'typeC')
AND p.event_id IS NULL; -- exclude already proceeded
poderia funcionar rápido o suficiente (pelo menos muito mais rápido que NOT IN
).