0

J'ai fait face à une situation embarrassante. Une requête avait un bon plan d'exécution. Mais lorsque cette requête a été utilisée comme une requête interne dans une requête plus importante, ce plan a changé. J'essaie de comprendre pourquoi cela pourrait être ainsi.Le plan d'exécution de la requête interne est différent lorsqu'il est exécuté dans le cadre d'une requête plus importante.

C'était sur Oracle 11g. Ma requête était:

SELECT * FROM YFS_SHIPMENT_H  
WHERE SHIPMENT_KEY IN 
    (
     SELECT DISTINCT SHIPMENT_KEY 
     FROM YFS_SHIPMENT_LINE_H 
     WHERE ORDER_HEADER_KEY = '20150113083918815889858' 
     OR (ORDER_LINE_KEY IN ( '20150113084438815896336')) 
    ); 

Comme vous pouvez le voir, il y a une requête intérieure ici, qui est la suivante:

SELECT DISTINCT SHIPMENT_KEY 
FROM YFS_SHIPMENT_LINE_H 
WHERE ORDER_HEADER_KEY = '20150113083918815889858' 
OR (ORDER_LINE_KEY IN ( '20150113084438815896336')) 

Quand je lance juste la requête interne, je reçois le plan d'exécution comme:

PLAN_TABLE_OUTPUT 
======================================================================================================== 
SQL_ID 3v82m4j5tv1k3, child number 0 
===================================== 
SELECT DISTINCT SHIPMENT_KEY FROM YFS_SHIPMENT_LINE_H WHERE 
ORDER_HEADER_KEY = '20150113083918815889858' OR (ORDER_LINE_KEY IN (
'20150113084438815896336')) 

Plan hash value: 3691773903 

======================================================================================================== 
| Id | Operation      | Name     | Rows | Bytes | Cost (%CPU)| Time  | 
======================================================================================================== 
| 0 | SELECT STATEMENT    |      |  |  | 10 (100)|   | 
| 1 | HASH UNIQUE     |      |  7 | 525 | 10 (10)| 00:00:01 | 
| 2 | CONCATENATION    |      |  |  |   |   | 
| 3 | TABLE ACCESS BY INDEX ROWID| YFS_SHIPMENT_LINE_H |  1 | 75 |  4 (0)| 00:00:01 | 
|* 4 |  INDEX RANGE SCAN   | YFS_SHIPMENT_LINE_H_I4 |  1 |  |  3 (0)| 00:00:01 | 
|* 5 | TABLE ACCESS BY INDEX ROWID| YFS_SHIPMENT_LINE_H |  6 | 450 |  5 (0)| 00:00:01 | 
|* 6 |  INDEX RANGE SCAN   | YFS_SHIPMENT_LINE_H_I6 |  6 |  |  3 (0)| 00:00:01 | 
======================================================================================================== 

Predicate Information (identified by operation id): 
=================================================== 

    4 = access("ORDER_LINE_KEY"='20150113084438815896336') 
    5 = filter(LNNVL("ORDER_LINE_KEY"='20150113084438815896336')) 
    6 = access("ORDER_HEADER_KEY"='20150113083918815889858') 

le plan d'exécution montre que le tableau est accessible YFS_SHIPMENT_LINE_H avec deux index YFS_SHIPMENT_LINE _H_I4 et YFS_SHIPMENT_LINE_H_I6; puis les résultats sont concaténés. Ce plan semble bien et le temps de réponse de la requête est génial.

Mais quand je lance la requête complète, le chemin d'accès des modifications de la requête interne comme donné ci-dessous:

PLAN_TABLE_OUTPUT 
======================================================================================================= 
SQL_ID dk1bp8p9g3vzx, child number 0 
===================================== 
SELECT * FROM YFS_SHIPMENT_H WHERE SHIPMENT_KEY IN (SELECT DISTINCT 
SHIPMENT_KEY FROM YFS_SHIPMENT_LINE_H WHERE ORDER_HEADER_KEY = 
'20150113083918815889858' OR (ORDER_LINE_KEY IN (
'20150113084438815896336'))) 

Plan hash value: 3651083773 

======================================================================================================= 
| Id | Operation     | Name     | Rows | Bytes | Cost (%CPU)| Time  | 
======================================================================================================= 
| 0 | SELECT STATEMENT    |      |  |  | 12593 (100)|   | 
| 1 | NESTED LOOPS    |      |  |  |   |   | 
| 2 | NESTED LOOPS    |      |  7 | 6384 | 12593 (1)| 00:02:32 | 
| 3 | SORT UNIQUE    |      |  7 | 525 | 12587 (1)| 00:02:32 | 
|* 4 |  INDEX FAST FULL SCAN  | YFS_SHIPMENT_LINE_H_I2 |  7 | 525 | 12587 (1)| 00:02:32 | 
|* 5 | INDEX UNIQUE SCAN   | YFS_SHIPMENT_H_PK  |  1 |  |  1 (0)| 00:00:01 | 
| 6 | TABLE ACCESS BY INDEX ROWID| YFS_SHIPMENT_H   |  1 | 837 |  2 (0)| 00:00:01 | 
======================================================================================================= 

Predicate Information (identified by operation id): 
=================================================== 

    4 = filter(("ORDER_HEADER_KEY"='20150113083918815889858' OR 
       "ORDER_LINE_KEY"='20150113084438815896336')) 
    5 = access("SHIPMENT_KEY"="SHIPMENT_KEY") 

S'il vous plaît noter que le YFS_SHIPMENT_LINE_H est maintenant accessible avec un indice différent (YFS_SHIPMENT_LINE_H_I2). Comme il s'avère, ce n'est pas un très bon index et le temps de réponse de la requête souffre.

Ma question est: Pourquoi le plan d'exécution de requête interne changerait-il lorsqu'il est exécuté dans le cadre de la requête plus grande? Une fois que l'optimiseur a trouvé le meilleur moyen d'accéder à YFS_SHIPMENT_LINE_H, pourquoi ne continuerait-il pas à utiliser le même plan d'exécution même s'il fait partie de la requête plus grande?

Remarque: Je ne suis pas trop préoccupé par ce qui serait le bon chemin d'accès ou l'index à utiliser; et par conséquent ne pas donner tous les index sur la table ici; et la cardinalité des données. Ma préoccupation concerne le changement lorsqu'il est exécuté séparément par rapport à une autre requête.

Merci.

- Parag

Répondre

0

Je ne sais pas pourquoi l'optimiseur Oracle décide de changer le chemin d'exécution. Mais, je pense que cela est une meilleure façon d'écrire la requête:

SELECT s.* 
FROM YFS_SHIPMENT_H s 
WHERE s.SHIPMENT_KEY IN (SELECT sl.SHIPMENT_KEY 
         FROM YFS_SHIPMENT_LINE_H sl 
         WHERE sl.ORDER_HEADER_KEY = '20150113083918815889858' 
         ) OR 
     s.SHIPMENT_KEY IN (SELECT sl.SHIPMENT_KEY 
         FROM YFS_SHIPMENT_LINE_H sl 
         WHERE sl.ORDER_LINE_KEY IN ('20150113084438815896336') 
         ); 

Notes:

  • Il n'y a pas besoin d'avoir SELECT DISTINCT dans une sous-requête pour IN. Je suis assez sûr qu'Oracle l'ignore, mais cela pourrait ajouter des frais généraux.
  • Diviser la logique en deux requêtes rend plus probable qu'Oracle peut utiliser des index pour la requête (les meilleurs sont sur YFS_SHIPMENT_LINE_H(ORDER_HEADER_KEY, SHIPMENT_KEY) et YFS_SHIPMENT_LINE_H(ORDER_LINE_KEY, SHIPMENT_KEY)).
+0

Vous avez raison, la requête peut en effet se faire d'une manière différente. Celui-ci se trouve être dans un code que je ne peux pas changer. ...... Aussi, vous faites un bon point sur ne pas avoir à utiliser 'distinct' dans le cadre de la clause' in'. –

0

Dans la première requête (non utilisé en tant que sous-requête), la table de base est accessible en fonction des conditions dans la clause where. Les index sur les deux colonnes impliquées sont utilisés pour accéder aux lignes.

Dans la requête complexe, vous effectuez une semi-jointure. L'optimiseur, à tort ou à raison, a décidé qu'il était plus efficace de lire les lignes de la table shipment, de lire le shipment_key et d'utiliser l'index sur shipment_key dans la table shipment_line pour récupérer les lignes pour voir si elles correspondent. Les conditions de la clause where sur la table shipment_line ne sont plus que des prédicats de filtre, elles ne sont pas utilisées pour déterminer les lignes à extraire de la table.

Si vous pensez que l'optimiseur s'est trompé (ce qui est possible, mais pas souvent avec des requêtes relativement simples comme celle-ci), assurez-vous que les statistiques sont à jour. Ce qui serait pertinent ici est la taille de chaque table, combien de lignes en moyenne ont le même shipment_key dans shipment_line, et la sélectivité des conditions dans la clause where dans la sous-requête. Gardez à l'esprit que pour la requête externe, il n'est pas nécessaire de calculer la sous-requête en entier (et très probablement Oracle ne la calcule pas entièrement); pour chaque ligne de la table shipment, dès qu'une ligne correspondante dans la table shipment_line est trouvée qui satisfait à la clause where, la recherche de cette shipment_key dans shipment_line s'arrête. Une chose que vous pouvez faire, si vous pensez vraiment que l'optimiseur a tort, c'est de voir ce qui se passe si vous utilisez des astuces. Par exemple, vous pouvez dire à l'optimiseur de ne pas utiliser l'index I2 sur shipment_line (prétendre que cela n'existe pas) - voir quel plan il proposera.

+0

vous avez mentionné que dans la requête complexe oracle lit d'abord les rangées de la table 'shipment', puis utilise la clé' shipment_key' pour lire la table 'line line '. Mais en regardant le plan d'exécution, j'ai senti que l'oracle lisait d'abord la «ligne d'expédition» (en utilisant l'analyse complète rapide sur 'yfs_shipment_line_h_i2'). Pouvez-vous élaborer s'il vous plaît? Merci. –

0

La jointure sur shipment_key force l'optimiseur à utiliser l'index le plus sélectif, en l'occurrence l'index YFS_SHIPMENT_LINE_H_I2. Sterling a créé cet index pour cette requête et il est incorrect. Déposez-le (ou rendez-le invisible) et regardez votre requête ramasser le bon plan. Si vous hésitez à supprimer l'index, car il fait partie du produit Sterling, utilisez les lignes de base SQL Plan Management.

YFS_SHIPMENT_LINE_H_I2 SHIPMENT_KEY 1 YFS_SHIPMENT_LINE_H_I2 ORDER_HEADER_KEY 2 YFS_SHIPMENT_LINE_H_I2 ORDER_RELEASE_KEY 3 YFS_SHIPMENT_LINE_H_I2 ORDER_LINE_KEY 4 YFS_SHIPMENT_LINE_H_I2 REQUESTED_TAG_NUMBER 5

+0

Pouvez-vous ajouter une brève explication sur ce que signifie le bloc de texte au bas de votre réponse? –

+0

Pourquoi pensez-vous que cet index est "faux"? De plus, vous parlez d'une "jointure" - vous réalisez que la requête ne fait pas de jointure, n'est-ce pas? – mathguy

+0

Peter, merci. Steve/Mathguy, Peter a répondu en fonction de la connaissance du produit qui fait l'appel - et pas seulement limitée à l'information fournie dans la question. Steve, le bloc de texte que Peter a ajouté à la fin est les colonnes utilisées par l'index YFS_SHIPMENT_LINE_H_I2. Je suppose que c'était une forme tabulaire pour commencer (Nom de l'index, Nom de la colonne, Position de la colonne), mais en quelque sorte a perdu son formatage. –