web-dev-qa-db-ja.com

Oracleで説明計画を理解するのに役立つ

いくつかの大きなテーブルでクエリを実行していますが、タフでも大量のデータで問題なく実行できますが、実行のどの部分が重要かを理解したいと思います。残念ながら、私は説明計画が苦手なので、助けを求めます。

これらのテーブルに関するデータは次のとおりです。

  • history_state_table7.424.65行(そのうち13.412のみがt1.alarm_type = 'AT1'の後に残されます)
  • costumer_price_history448.284.169
  • cycle_table215

これはクエリになります(ロジックを気にしないでください。参照用です):

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)

そして最後に説明計画:

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    

「より効率的に書き換える方法」ではなく、説明計画を使用せずに、最もコストのかかる操作をどのように見つけるかを尋ねていることに注意してください。その間私はそれについて読んでいますが、私はいくつかの助けに感謝します。

6
filippo

Explainプランは、実際に最もコストのかかる「操作」を教えてくれません。 「Cost」列はguessです-オプティマイザによって推定された値です。 「カーディナリティ」列と「バイト」列も同様です。 http://docs.Oracle.com/cd/B28359_01/server.111/b28274/ex_plan.htm#i183

あなたの例では、あなたのオプティマイザはあなたに言っています:私はこのプランを使用することを決定しますなぜならループは約5,483のコストがかかると思いますそして、これが実行の最もコストのかかる部分になることを願っていますが、これを保証することはできません。

同じことがツリーのすべての深さに再帰的に適用されます。

最も低いレベル(つまり、直感的に最もループされ、最も実行されたレベル)まで詳細に進むと、予想コストと予想要素数の両方に関して、特に突出している操作が

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

したがって、オプティマイザは、このクエリの最適な実行が、貧弱な主力のRAIDPIDAT.IDX_HISTORY_STATE_TABLE_1TPALMの周りをループすることであると推測しました。クエリのどの部分が直接それに関連しているのか実際にはわかりませんが、t1.data_tratado状態が疑われます。そして、繰り返しますが、それがisが本当に最もコストのかかる部分であるかどうかはわかりません。

Explain Planのループの構文を手続き型の疑似コードに変換してみます。

/* 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

複雑なようですが、各「ネストされたループ」の目的は、最も単純な方法であるループの内側のループで結合(2つのテーブルで構成される単一のテーブル)を作成することです。

3
kubanczyk

説明プランは、クエリの実行時に使用される結合メソッドの予測にすぎません。これにより、ステートメントの実行時に別の計画が実行される可能性があるため、どのステップが最も時間がかかるかを推測することが難しくなります。

各ステップにかかる時間に関する実際の統計を取得するには、ステートメントの sql trace を実行してトレースファイルを確認する必要があります-手動で、または tkprof などのツールを使用して。これにより、各ステップが処理された行数と所要時間が表示されます。

つまり、各行の終わりにリストされているCardinalityを見ると、処理される行数がわかります。より多くの行を処理するステップは、実行する作業が増えるため、実行に時間がかかる可能性があります。

したがって、例の行では6 INDEX RANGE SCAN INDEX RAIDPIDAT.IDX_HISTORY_STATE_TABLE_1TPALM Cost: 662 Cardinality: 102,068他のステップが1つの行を予測しているため、102,068行を処理すると予想される場合は、最もコストがかかる可能性があります。ただし、これは、これらのカーディナリティの推定値が正確な場合にのみ当てはまります。これらのカーディナリティが実際に返される行と一致することを確認する必要があります。これを行う最も簡単な方法は、上記のSQLトレースを使用することです。

1
Chris Saxon