web-dev-qa-db-fra.com

Aider à comprendre expliquer le plan d'Oracle

Je gère une requête dans de grandes tables, et bien que cela fonctionne bien, même difficile, c'est de nombreuses données, j'aimerais comprendre quelle partie de celle-ci pèse sur l'exécution. Malheureusement, je ne suis pas trop bon avec Expliquer les plans, alors j'appelle de l'aide.

Voici quelques données sur ces tables:

  • history_state_table7.424.65 lignes (dont seule 13.412 reste après t1.alarm_type = 'AT1')
  • costumer_price_history448.284.169 rangées
  • cycle_table215 rangées

Ce serait la requête (ne vous dérange pas que la logique est juste pour la référence):

SELECT t1.id_alarm, t2.load_id, t2.reference_date
  FROM history_state_table t1,
       (SELECT   op_code, contract_num,
                 COUNT (DISTINCT id_ponto) AS num_pontos,
                 COUNT
                    (DISTINCT CASE
                        WHEN vlr > 0
                           THEN id_ponto
                        ELSE NULL
                     END
                    ) AS bigger_than_zero,
                 MAX (load_id) AS load_id,
                 MAX (reference_date) AS reference_date
            FROM costumer_price_history
           WHERE load_id IN
                            (42232, 42234, 42236, 42238, 42240, 42242, 42244) /* arbitrary IDs depending on execution*/
             AND sistema = 'F1'          /* Hardcoded filters */
             AND rec_type = 'F3'         /* Hardcoded filters */
             AND description = 'F3'      /* Hardcoded filters */
             AND extract_type IN
                    ('T1', 'T2', 'T3')
        GROUP BY op_code, contract_num) t2
 WHERE t1.op_code = t2.op_code
   AND t1.contract_num = t2.contract_num
   AND t1.alarm_type = 'AT1'
   AND t1.alarm_status = 'DONE'
   AND (   (    t1.prod_type = 'COMBO'
            AND t2.bigger_than_zero = t2.num_pontos - 1
           )
        OR (    t1.prod_type != 'COMBO'
            AND t2.bigger_than_zero = t2.num_pontos
           )
       )
       /* arbitrary filter depending on execution*/
   AND t1.data_tratado BETWEEN (SELECT data_inicio
                                  FROM cycle_table
                                 WHERE id_ciclo = 160) AND (SELECT data_fim
                                                              FROM cycle_table
                                                             WHERE id_ciclo =
                                                                           160)

Et enfin le plan d'explication:

Plan
SELECT STATEMENT  ALL_ROWSCost: 5,485                           
    13 NESTED LOOPS                         
        7 NESTED LOOPS  Cost: 5,483  Bytes: 115  Cardinality: 1                     
            5 VIEW  Cost: 12  Bytes: 59  Cardinality: 1                 
                4 SORT GROUP BY  Cost: 12  Bytes: 85  Cardinality: 1            
                    3 INLIST ITERATOR       
                        2 TABLE ACCESS BY INDEX ROWID TABLE RAIDPIDAT.COSTUMER_PRICE_HISTORY Cost: 11  Bytes: 85  Cardinality: 1    
                            1 INDEX RANGE SCAN INDEX RAIDPIDAT.IDX_COSTUMER_PRICE_HISTORY_2 Cost: 10  Cardinality: 3  
            6 INDEX RANGE SCAN INDEX RAIDPIDAT.IDX_HISTORY_STATE_TABLE_1TPALM Cost: 662  Cardinality: 102,068               
        12 TABLE ACCESS BY INDEX ROWID TABLE RAIDPIDAT.HISTORY_STATE_TABLE Cost: 5,471  Bytes: 56  Cardinality: 1                   
            9 TABLE ACCESS BY INDEX ROWID TABLE RAIDPIDAT.CYCLE_TABLE Cost: 1  Bytes: 12  Cardinality: 1                
                8 INDEX UNIQUE SCAN INDEX (UNIQUE) RAIDPIDAT.PK_CYCLE_TABLE Cost: 0  Cardinality: 1             
            11 TABLE ACCESS BY INDEX ROWID TABLE RAIDPIDAT.CYCLE_TABLE Cost: 1  Bytes: 12  Cardinality: 1               
                10 INDEX UNIQUE SCAN INDEX (UNIQUE) RAIDPIDAT.PK_CYCLE_TABLE Cost: 0  Cardinality: 1    

L'esprit que je ne demande pas "comment le réécrire plus efficacement", mais comment puis-je trouver avec le plan d'explication de l'opération la plus coûteuse. Pendant ce temps, je lis à ce sujet, mais j'apprécierais de l'aide.

6
filippo

Expliquer Plan ne vous dit pas ce qui est en réalité l'opération la plus coûteuse. La colonne "Coût" est un Guess - il s'agit d'une valeur estimée par optimiseur. La colonne "Cardinalité" et la colonne "octets". http://docs.oracle.com/cd/b28359_01/server.111/b28274/ex_plan.htm#i183

Dans votre exemple, votre optimiseur vous dit: Je décide d'utiliser ce plan car Je suppose que la boucle coûterait environ 5 483. Et j'espère que ce serait la partie la plus coûteuse de l'exécution, mais je ne peux pas garantir cela.

La même chose s'applique récursivement à toutes les profondeurs de l'arbre.

Si vous allez en profondeur aux niveaux les plus bas (c'est-à-dire par intuition la plupart des niveaux en boucle, les niveaux la plupart exécutés), vous voyez que l'opération qui s'en tient particulièrement en termes de coût attendu et de nombre attendu d'éléments, est la

6 INDEX RANGE SCAN INDEX RAIDPIDAT.IDX_HISTORY_STATE_TABLE_1TPALM Cost: 662  Cardinality: 102,068 

Ainsi, Optimiseur devinait que l'exécution optimale de cette requête est de boucler beaucoup autour d'un mauvais cheval de travail raidpidat.idx_history_state_table_1tpalm. Je ne peux vraiment pas voir quelle partie de votre requête le concerne directement, mais je soupçonne que t1.data_tratado condition. Et encore une fois, je ne peux pas voir si elle est vraiment la partie la plus coûteuse.

Je vais essayer de traduire la syntaxe des boucles dans le plan d'explication au pseudo-code procédural:

/* begin step 13 (by "step 13" I mean a line that reads "   13 NESTED LOOPS") */
  /* begin step 7 */
    do step 5
    myresult = rows from step 5
    for each row from myresult {
       do step 6
       for each row from step 6 {
           join to a row from myresult the matching row from step 6
       }
    }
  /* end step 7 */
  for each row from myresult {
     do step 12
     for each row from step 12 {
         join to a row from myresult the matching row from step 12
     }
  }
/* end step 13 */
return myresult

On dirait compliqué, mais un but vraiment de chaque "boucle imbriquée" est de créer une jointure (une seule table faite de deux tables) de la manière la plus naïve, une boucle-inside-a-boucle.

3
kubanczyk

Le plan d'explication n'est qu'une prédiction des méthodes de jointure qui seront utilisées lors de l'exécution d'une requête. Cela peut empêcher de déduire quelle étape prend le plus de temps, car un plan différent peut être suivi lorsque la déclaration est exécutée.

Pour obtenir des statistiques réelles sur la durée de la durée de chaque étape, vous devez exécuter une SQL TRACE de l'instruction et consultez le fichier de trace - manuellement ou à l'aide d'un outil tel que TKProf . Cela vous montrera combien de lignes chaque étape traitée et combien de temps il a fallu.

Cela dit, en regardant le Cardinality inscrit à la fin de chaque ligne donnera une indication de combien de lignes doivent être traitées. Étapes Traitement Plusieurs rangées sont susceptibles de prendre plus de temps à exécuter car il y a plus de travail à faire.

Donc dans votre exemple ligne 6 INDEX RANGE SCAN INDEX RAIDPIDAT.IDX_HISTORY_STATE_TABLE_1TPALM Cost: 662 Cardinality: 102,068 qui devrait traiter 102 068 lignes est susceptible d'être la plus chère que les autres étapes prédisent une rangée. Ceci n'est vrai que si ces estimations de cardinalité sont toutefois précises; Vous devrez vérifier que ces cardinalités correspondent aux lignes réelles retournées. Le moyen le plus simple de le faire est via une trace SQL comme indiqué ci-dessus.

1
Chris Saxon