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.

Était-ce utile?

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).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top