Oracle Anti-Join question Plan d'exécution
-
20-09-2019 - |
Question
Nous avons deux tables comme ceci:
Event
id
type
... a bunch of other columns
ProcessedEvent
event_id
process
Il existe des indices définis pour
- Event (id) (PK)
- ProcessedEvent (event_id, process)
Le premier représente les événements dans une application.
Le second représente le fait qu'un certain événement a obtenu des processus par un certain processus. Il y a beaucoup de processus qui ont besoin de traiter un événement, donc il y a plusieurs entrées dans la deuxième table pour chaque entrée dans le premier.
Afin de trouver tous les événements qui ont besoin d'un traitement, nous exécutons la requête suivante:
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
)
Les statistiques sont à jour
Étant donné que la plupart des événements sont traités, je pense que le meilleur plan d'exécution devrait ressembler à ceci
- analyse complète de l'index sur l'indice ProcessedEvent
- analyse complète de l'index sur l'indice de l'événement
- Anti jointure entre les deux
- accès à la table avec le reste
- Filtre
Au lieu Oracle effectue les opérations suivantes
- analyse complète de l'index sur l'indice ProcessedEvent
- scan complet de table sur la table de l'événement
- filtrer la table d'événements
- Anti jointure entre les deux ensembles
Avec un indicateur d'index je reçois Oracle pour effectuer les opérations suivantes:
- analyse complète de l'index sur l'indice ProcessedEvent
- analyse complète de l'index sur l'indice de l'événement
- accès de table sur la table d'événements
- filtrer la table d'événements
- Anti jointure entre les deux ensembles
ce qui est à mon humble avis vraiment stupide.
Alors ma question est: quelle pourrait être la raison pour oracle d'insister sur l'accès à la table début
Addition: La performance est mauvaise. Nous fixons le problème de performances en sélectionnant seulement les Event.IDs puis aller chercher les lignes nécessaires « manuellement ». Mais bien sûr, qui est juste un travail autour.
La solution
votre FULL SCAN INDEX sera probablement plus rapide qu'un COMPLET TABLE SCAN puisque l'indice est probablement « plus mince » que la table. Pourtant, FULL SCAN INDEX est une lecture de segment complet et il sera sur le même coût que FULL TABLE SCAN.
Cependant, vous ajoutez également TABLE ACCESS par étape ROWID. Il est une étape coûteuse: un IO logique par ligne pour l'accès ROWID alors que vous obtenez une logique IO par blocs multiples (en fonction de votre db_file_multiblock_read_count parameter
) pour la pleine TABLE SCAN.
En conclusion, l'optimiseur calcule que:
cost(FULL TABLE SCAN) < cost(FULL INDEX SCAN) + cost(TABLE ACCESS BY ROWID)
Mise à jour : Le COMPLET TABLE SCAN permet également le filtre du type plus tôt que dans le chemin de FULL SCAN INDEX (puisque l'indice ne sait pas quel type d'événement est), ce qui réduit la taille l'ensemble qui sera anti-joint (encore un autre avantage de la pleine TABLE SCAN).
Autres conseils
L'optimiseur fait beaucoup de choses qui ne font pas de sens dans un premier temps, mais il a ses raisons. Ils ne peuvent pas toujours être droite , mais ils sont compréhensibles.
Le tableau de l'événement peut être plus facile à balayage complet plutôt que par un accès rowid en raison de sa taille. Il pourrait être qu'il ya beaucoup moins d'opérations d'entrées-sorties impliquées pour lire toute la table séquentielle que de lire des morceaux.
est la performance mauvaise, ou êtes-vous simplement demander pourquoi l'optimiseur a fait cela?
Je ne peux pas expliquer le comportement de l'optimiseur, mais mon expérience a été d'éviter « NOT IN » à tout prix, pour le remplacer au lieu de MINUS, comme ceci:
select * from Event
where id in (
select id from Event where type in ( 'typeA', 'typeB', 'typeC')
minus
select id from ProcessedEvent
)
J'ai vu des ordres de grandeur dans les performances des requêtes avec des transformations similaires.
Quelque chose comme:
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
pourrait travailler assez vite (au moins beaucoup plus rapide que NOT IN
).