jeudi 24 novembre 2011

"View merging" et "Predicate pushing"

Le principe du “view merging” correspond à la capacité pour l’optimiseur de pouvoir transformer une requête SQL qui appelle une vue afin de fusionner cette vue au sein de la requête. Lorsqu’une vue est mergée, le code de la vue n’est plus exécutée à part mais fait partie intégrante de la requête principale.

Le fait de merger une vue peut être choisi par l’optimiseur afin par exemple d’appliquer à la vue une clause WHERE se trouvant à l’extérieur de la vue et ainsi limiter le nombre de lignes retournées par une table de la vue.

Exemple :
SQL> create table t1 as select * from dba_objects;

Table created.

SQL> insert into t1 select * from t1;

72706 rows created.

SQL> insert into t1 select * from t1;

145412 rows created.

SQL> create index idx_obj_id on t1(object_id);

Index created.

SQL> create index idx_obj_type on T1(object_type);

Index created.

SQL> exec dbms_stats.gather_table_stats(user,'T1');

PL/SQL procedure successfully completed.

SQL> create view v1 as select * from t1;

View created.

J’ai une table qui contient environ 290 000 lignes, un index B-TREE sur la colonne OBJECT_TYPE, un index B-TREE sur la colonne OBJECT_ID et une vue V1 qui sélectionne les lignes de la table T1.

La requête suivante effectue une jointure entre T1 et V1 en filtrant les objets de type EDITION :
select t1.object_name 
  from V1, T1 
 where t1.object_type = 'EDITION' 
   and t1.object_id = v1.object_id; 

Voyons ce que donne le plan lorsque la vue n’est pas mergée :
---------------------------------------------------------------------------------------------------------------- 
| Id  | Operation                    | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | 
---------------------------------------------------------------------------------------------------------------- 
|   0 | SELECT STATEMENT             |              |      1 |        |     16 |00:00:00.50 |    4147 |   4136 | 
|*  1 |  HASH JOIN                   |              |      1 |    106 |     16 |00:00:00.50 |    4147 |   4136 | 
|   2 |   TABLE ACCESS BY INDEX ROWID| T1           |      1 |     27 |      4 |00:00:00.01 |       7 |      0 | 
|*  3 |    INDEX RANGE SCAN          | IDX_OBJ_TYPE |      1 |     27 |      4 |00:00:00.01 |       3 |      0 | 
|   4 |   VIEW                       | V1           |      1 |    290K|    290K|00:00:00.31 |    4140 |   4136 | 
|   5 |    TABLE ACCESS FULL         | T1           |      1 |    290K|    290K|00:00:00.18 |    4140 |   4136 | 
---------------------------------------------------------------------------------------------------------------- 
 Predicate Information (identified by operation id): 
--------------------------------------------------- 
   1 - access("T1"."OBJECT_ID"="V1"."OBJECT_ID") 
   3 - access("T1"."OBJECT_TYPE"='EDITION') 

L’opération 4 « VIEW » indique que le SELECT de la vue est exécuté à part et puisque dans le code de la vue il n’y a pas de clause WHERE, un Full Scan de la table T1 est effectué car toutes les lignes doivent être ramenées.

En revanche, la table T1 de la requête principale (outer query) est bien accédée via l’index IDX_OBJ_TYPE car le prédicat sur la colonne OBEJCT_TYPE est bien pris en compte.

Voyons maintenant le plan lorsque la vue est mergée :
---------------------------------------------------------------------------------------------------------------- 
| Id  | Operation                    | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | 
---------------------------------------------------------------------------------------------------------------- 
|   0 | SELECT STATEMENT             |              |      1 |        |     16 |00:00:00.04 |      21 |     10 | 
|   1 |  NESTED LOOPS                |              |      1 |    106 |     16 |00:00:00.04 |      21 |     10 | 
|   2 |   TABLE ACCESS BY INDEX ROWID| T1           |      1 |     27 |      4 |00:00:00.02 |       9 |      7 | 
|*  3 |    INDEX RANGE SCAN          | IDX_OBJ_TYPE |      1 |     27 |      4 |00:00:00.01 |       5 |      3 | 
|*  4 |   INDEX RANGE SCAN           | IDX_OBJ_ID   |      4 |      4 |     16 |00:00:00.01 |      12 |      3 | 
---------------------------------------------------------------------------------------------------------------- 

Predicate Information (identified by operation id): 
--------------------------------------------------- 
   3 - access("T1"."OBJECT_TYPE"='EDITION') 
   4 - access("T1"."OBJECT_ID"="OBJECT_ID") 

Il n’y a plus d’opération VIEW dans le plan, c’est donc que la vue a été mergée.

La colonne Buffers du plan montre que le nombre de logical reads effectués est de 21. Avec la vue non mergée le coût était de 4147 logical reads.

Ce gain s’explique par le fait que le CBO a transformé la requête pour intégrer le code de la vue dans la requête principale. La requête devient en fait la suivante :
select t1.object_name 
from T1, T1 v1 
where t1.object_type = 'EDITION' 
and t1.object_id = v1.object_id; 

Cette transformation permet un accès indéxé pour les 2 tables beaucoup plus efficace que lorsque la vue doit être exécutée à part.

Pour obtenir le plan avec la vue non mergée dans mon exemple précédent, j’ai ajouté le hint NO_MERGE dans le code de la vue :
create or replace view v1 as select /*+ no_merge */ * from t1; 

Ce hint indique à l’optimiseur que la vue ne doit pas être mergée pendant la phase de transformation de la requête. Si vous êtes dans un cas où le CBO a décidé de ne pas merger la vue, vous pouvez forcer le merge en utilisant le hint MERGE.

Toutefois, il existe des cas où le merge peut être considéré comme non applicable par le CBO. C’est le cas parfois lorsque la vue contient par exemple:
  • une fonction analytique (ex : LEAD, LAG, RANK, ROW_NUMBER etc.)
  • une fonction d’agrégation (ex : SUM, AVG, MAX, MIN etc.)
  • la colonne ROWNUM
  • la clause ORDER BY

En effet, le fait de merger la vue dans ces cas là peut avoir une influence sur le résultat de la requête. Si le fait de merger peut influencer le résultat alors le CBO s’interdit de merger pendant la phase de transformation de la requête.

Exemple :
Je rajoute la colonne ROWNUM à ma vue V1 :
create or replace view v1 as select  t1.*,rownum RN from t1; 

J’exécute une requête simple avec un prédicat sur la colonne OBJECT_TYPE :
select * from v1 where object_type='EDITION'; 

  ----------------------------------------------------------------------------------------------- 
| Id  | Operation           | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | 
----------------------------------------------------------------------------------------------- 
|   0 | SELECT STATEMENT    |      |      1 |        |      4 |00:00:00.37 |    4139 |   4135 | 
|*  1 |  VIEW               | V1   |      1 |    290K|      4 |00:00:00.37 |    4139 |   4135 | 
|   2 |   COUNT             |      |      1 |        |    290K|00:00:00.27 |    4139 |   4135 | 
|   3 |    TABLE ACCESS FULL| T1   |      1 |    290K|    290K|00:00:00.14 |    4139 |   4135 | 
----------------------------------------------------------------------------------------------- 

Predicate Information (identified by operation id): 
--------------------------------------------------- 
   1 - filter("OBJECT_TYPE"='EDITION') 

On s’aperçoit que la vue n’est pas mergée. Oracle doit attaquer la table T1 en Full Table Scan pour ensuite filtrer les lignes sur la colonne OBJECT_TYPE. Ce plan s’explique par la présence de la colonne ROWNUM qui serait différent si on appliquait très tôt la clause WHERE sur la colonne OBJECT_TYPE.

Il existe aussi des cas où même quand la vue ne peut être mergée le CBO peut tout de même se permettre d’appliquer les prédicats ,qui se trouvent au niveau de la requête principale, aux tables de la vue. Le but est de pouvoir filtrer les données au plus tôt au niveau du plan et/ou de pouvoir utiliser un index plus efficace.

Exemple :

La requête suivante donne un plan avec une vue complètement mergée et un accès indexé au niveau de la table T1 :
select * from ( select  t1.* from t1) v1 where object_type='EDITION'; 

------------------------------------------------------------------------------------------------------ 
| Id  | Operation                   | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | 
------------------------------------------------------------------------------------------------------ 
|   0 | SELECT STATEMENT            |              |      1 |        |      4 |00:00:00.01 |       8 | 
|   1 |  TABLE ACCESS BY INDEX ROWID| T1           |      1 |     27 |      4 |00:00:00.01 |       8 | 
|*  2 |   INDEX RANGE SCAN          | IDX_OBJ_TYPE |      1 |     27 |      4 |00:00:00.01 |       4 | 
------------------------------------------------------------------------------------------------------ 

Predicate Information (identified by operation id): 
--------------------------------------------------- 
2 - access("T1"."OBJECT_TYPE"='EDITION') 

Voyons ce qui se passe si j’empêche le merge de la vue grâce au hint NO_MERGE :
select * from ( select /*+ no_merge */  t1.* from t1) v1 
where object_type='EDITION'; 
  
------------------------------------------------------------------------------------------------------- 
| Id  | Operation                    | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | 
------------------------------------------------------------------------------------------------------- 
|   0 | SELECT STATEMENT             |              |      1 |        |      4 |00:00:00.01 |       8 | 
|   1 |  VIEW                        |              |      1 |     27 |      4 |00:00:00.01 |       8 | 
|   2 |   TABLE ACCESS BY INDEX ROWID| T1           |      1 |     27 |      4 |00:00:00.01 |       8 | 
|*  3 |    INDEX RANGE SCAN          | IDX_OBJ_TYPE |      1 |     27 |      4 |00:00:00.01 |       4 | 
------------------------------------------------------------------------------------------------------- 

Predicate Information (identified by operation id): 
--------------------------------------------------- 
   3 - access("T1"."OBJECT_TYPE"='EDITION') 

L’opération VIEW à la ligne 1 indique que la vue n’est pas mergée. Par contre on s’aperçoit qu’au lieu d’avoir un Full Table Scan on a un accès indexé. La clause WHERE sur la colonne OBJECT_TYPE a été pushée à l’intérieur de la vue afin de bénéficier de l’utilisation de l’index. Cette capacité à pusher les prédicats se nomme « Predicate Pushing ».

Normalement, le hint NO_PUSH_PRED devrait empêcher l’optimiseur de faire un « push down » des predicats, mais je ne sais pas pourquoi il ne fonctionne pas dans mon cas :
select /*+ NO_PUSH_PRED(v1) */ * from ( select /*+ no_merge */  t1.* 
from t1) v1 where object_type='EDITION'; 
------------------------------------------------------------------------------------------------------- 
| Id  | Operation                    | Name         | Starts | E-Rows | A-Rows |   A-Time   | Buffers | 
------------------------------------------------------------------------------------------------------- 
|   0 | SELECT STATEMENT             |              |      1 |        |      4 |00:00:00.01 |       8 | 
|   1 |  VIEW                        |              |      1 |     27 |      4 |00:00:00.01 |       8 | 
|   2 |   TABLE ACCESS BY INDEX ROWID| T1           |      1 |     27 |      4 |00:00:00.01 |       8 | 
|*  3 |    INDEX RANGE SCAN          | IDX_OBJ_TYPE |      1 |     27 |      4 |00:00:00.01 |       4 | 
------------------------------------------------------------------------------------------------------- 

Predicate Information (identified by operation id): 
--------------------------------------------------- 
   3 - access("T1"."OBJECT_TYPE"='EDITION') 

Du coup pour avoir une idée du plan sans le predicate pushing, j’ai ajouté la colonne ROWNUM dans la vue :
select * from ( select /*+ no_merge */  t1.* from t1 WHERE rownum > 1) v1  
where object_type='EDITION'; 
----------------------------------------------------------------------------- 
| Id  | Operation            | Name | Rows  | Bytes | Cost (%CPU)| Time     | 
----------------------------------------------------------------------------- 
|   0 | SELECT STATEMENT     |      |   290K|    57M|  1127   (1)| 00:00:14 | 
|*  1 |  VIEW                |      |   290K|    57M|  1127   (1)| 00:00:14 | 
|   2 |   COUNT              |      |       |       |            |          | 
|*  3 |    FILTER            |      |       |       |            |          | 
|   4 |     TABLE ACCESS FULL| T1   |   290K|    26M|  1127   (1)| 00:00:14 | 
----------------------------------------------------------------------------- 

Predicate Information (identified by operation id): 
--------------------------------------------------- 
   1 - filter("OBJECT_TYPE"='EDITION') 
   3 - filter(ROWNUM>1) 

On voit bien que quand le prédicat sur la colonne OBJECT_TYPE n’est pas pushé dans la vue, l’index de T1 ne peut être utilisé et on obtient donc un TABLE ACCESS FULL.

J’ai pensé à écrire cet article suite à un problème de perf rencontrée sur une requête faisant référence à une vue contenant notamment une fonction analytique.

La requête était la suivante :
SELECT task_instance_id, task_class_id, status, class_rank, instance_number 
  FROM (select 
         ti.task_instance_id as task_instance_id, 
         tc.task_class_id as task_class_id, 
         ti.status, 
         Rank() over(PARTITION BY tc.task_class_id ORDER BY ti.task_instance_id) AS class_rank, 
tc.instance_number, 
         tc.poll_max_grab 
          from mdw_task_instance      ti, 
               mdw_task_type          tt, 
               mdw_task_class         tc, 
               mdw_batch_engine_group eg, 
               mdw_batch_engine       be 
         where ti.status = 0 
           and ti.app_code = tt.app_code 
           and ti.task_label = tt.task_label 
           and tt.task_class_id = tc.task_class_id 
           and tc.batch_group_id = eg.batch_group_id 
and eg.batch_engine_id = be.batch_engine_id 
           and be.label = 'RLX000') poll_req 
 WHERE (instance_number <= 0 OR 
       (class_rank <= poll_req.poll_max_grab AND 
       0 <= instance_number - class_rank - 
       (select count(*) 
                 from mdw_task_instance ti, mdw_task_type tt 
                where ti.status in (1) 
                  and ti.app_code = tt.app_code 
                  and ti.task_label = tt.task_label 
                  and tt.task_class_id = poll_req.task_class_id))) 
 order by task_instance_id; 

----------------------------------------------------------------------------------------------------------------------------------------------- 
| Id  | Operation                           | Name                        | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | Writes | 
----------------------------------------------------------------------------------------------------------------------------------------------- 
|   0 | SELECT STATEMENT                    |                             |      1 |        |      0 |00:00:02.28 |    5102 |   6781 |   6778 | 
|   1 |  SORT ORDER BY                      |                             |      1 |  64486 |      0 |00:00:02.28 |    5102 |   6781 |   6778 | 
|*  2 |   FILTER                            |                             |      1 |        |      0 |00:00:02.28 |    5102 |   6781 |   6778 | 
|   3 |    VIEW                             |                             |      1 |  64486 |    311K|00:00:02.23 |    5102 |   6781 |   6778 | 
|   4 |     WINDOW SORT                     |                             |      1 |  64486 |    311K|00:00:02.15 |    5102 |   6781 |   6778 | 
|   5 |      NESTED LOOPS                   |                             |      1 |  64486 |    311K|00:00:00.29 |    5100 |      3 |      0 | 
|   6 |       NESTED LOOPS                  |                             |      1 |     29 |     93 |00:00:00.07 |       8 |      2 |      0 | 
|*  7 |        HASH JOIN                    |                             |      1 |    175 |    108 |00:00:00.07 |       5 |      2 |      0 | 
|   8 |         MERGE JOIN                  |                             |      1 |     97 |     98 |00:00:00.07 |       4 |      2 |      0 | 
|   9 |          TABLE ACCESS BY INDEX ROWID| MDW_TASK_CLASS              |      1 |     94 |     94 |00:00:00.05 |       3 |      1 |      0 | 
|  10 |           INDEX FULL SCAN           | MDW_TASK_CLASS_PK           |      1 |     94 |     94 |00:00:00.05 |       1 |      1 |      0 | 
|* 11 |          SORT JOIN                  |                             |     94 |     97 |     98 |00:00:00.02 |       1 |      1 |      0 | 
|* 12 |           INDEX FULL SCAN           | MDW_TASK_TYPE_IDX1          |      1 |     97 |     98 |00:00:00.02 |       1 |      1 |      0 | 
|  13 |         INDEX FULL SCAN             | MDW_BATCH_ENGINE_GROUP_IDX1 |      1 |     18 |     18 |00:00:00.01 |       1 |      0 |      0 | 
|* 14 |        INDEX RANGE SCAN             | MDW_BATCH_ENGINE_IDX1       |    108 |      1 |     93 |00:00:00.01 |       3 |      0 |      0 | 
|* 15 |       INDEX RANGE SCAN              | MDW_TASK_INSTANCE_IDX4      |     93 |   2216 |    311K|00:00:00.16 |    5092 |      1 |      0 | 
|  16 |    SORT AGGREGATE                   |                             |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |      0 | 
|  17 |     NESTED LOOPS                    |                             |      0 |     40 |      0 |00:00:00.01 |       0 |      0 |      0 | 
|* 18 |      INDEX RANGE SCAN               | MDW_TASK_TYPE_IDX2          |      0 |      1 |      0 |00:00:00.01 |       0 |      0 |      0 | 
|* 19 |      INDEX RANGE SCAN               | MDW_TASK_INSTANCE_IDX4      |      0 |     36 |      0 |00:00:00.01 |       0 |      0 |      0 | 
----------------------------------------------------------------------------------------------------------------------------------------------- 

Predicate Information (identified by operation id): 
--------------------------------------------------- 
   2 - filter(("INSTANCE_NUMBER"<=0 OR ("CLASS_RANK"<="POLL_REQ"."POLL_MAX_GRAB" AND "INSTANCE_NUMBER"-"CLASS_RANK"->=0))) 
   7 - access("TC"."BATCH_GROUP_ID"="EG"."BATCH_GROUP_ID") 
  11 - access("TT"."TASK_CLASS_ID"="TC"."TASK_CLASS_ID") 
       filter("TT"."TASK_CLASS_ID"="TC"."TASK_CLASS_ID") 
  12 - filter("TT"."TASK_CLASS_ID" IS NOT NULL) 
  14 - access("BE"."LABEL"='RLX000' AND "EG"."BATCH_ENGINE_ID"="BE"."BATCH_ENGINE_ID") 
  15 - access("TI"."STATUS"=0 AND "TI"."APP_CODE"="TT"."APP_CODE" AND "TI"."TASK_LABEL"="TT"."TASK_LABEL") 
  18 - access("TT"."TASK_CLASS_ID"=:B1) 
  19 - access("TI"."STATUS"=1 AND "TI"."APP_CODE"="TT"."APP_CODE" AND "TI"."TASK_LABEL"="TT"."TASK_LABEL") 

Le plan génère 5102 logical reads pour retourner 0 ligne. C’est beaucoup trop.

Le problème vient du fait que la clause la plus filtrante est la suivante : « instance_number <= 0 »

Cette clause se situe en dehors de la vue POLL_REQ mais s’applique à une colonne retournée par la vue. Comme la vue contient une fonction analytique le CBO ne peut ni merger la vue ni effectuer un push down des prédicats. Le filtrage des lignes ne s’effectue alors qu’après exécution de la vue. L’opération 2 du plan nommé VIEW indique que la vue n’a pas été mergée et la section « Predicate Information » du plan pour l’opération 2 indique que le filtrage des lignes sur notamment la colonne INSTANCE_NUMBER n’est effectué qu’après que la vue ait été exécutée.

En ajoutant le filtre sur la colonne INSTANCE_NUMBER à l’intérieur de la vue, on obtient un plan différent et beaucoup moins couteux :
SELECT  task_instance_id, task_class_id, status, class_rank, instance_number 
  FROM (select 
         ti.task_instance_id as task_instance_id, 
         tc.task_class_id as task_class_id, 
         ti.status, 
         Rank() over(PARTITION BY tc.task_class_id ORDER BY ti.task_instance_id) AS class_rank, 
         tc.instance_number, 
         tc.poll_max_grab 
          from mdw_task_instance      ti, 
               mdw_task_type          tt, 
               mdw_task_class         tc, 
               mdw_batch_engine_group eg, 
               mdw_batch_engine       be 
         where ti.status = 0 
           and ti.app_code = tt.app_code 
           and ti.task_label = tt.task_label 
           and tt.task_class_id = tc.task_class_id 
           and tc.batch_group_id = eg.batch_group_id 
           and eg.batch_engine_id = be.batch_engine_id 
           and be.label = 'RLX000' 
                                   and tc.instance_number <= 0 -- ajout 
                                   ) poll_req 
 WHERE (instance_number <= 0 OR 
       (class_rank <= poll_req.poll_max_grab AND 
       0 <= instance_number - class_rank - 
       (select count(*) 
                 from mdw_task_instance ti, mdw_task_type tt 
                where ti.status in (1) 
                  and ti.app_code = tt.app_code 
                  and ti.task_label = tt.task_label 
                  and tt.task_class_id = poll_req.task_class_id))) 
 order by task_instance_id; 
-------------------------------------------------------------------------------------------------------------------------------------- 
| Id  | Operation                           | Name                        | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | 
-------------------------------------------------------------------------------------------------------------------------------------- 
|   0 | SELECT STATEMENT                    |                             |      1 |        |      0 |00:00:00.05 |       1 |      1 | 
|   1 |  SORT ORDER BY                      |                             |      1 |    347 |      0 |00:00:00.05 |       1 |      1 | 
|*  2 |   FILTER                            |                             |      1 |        |      0 |00:00:00.05 |       1 |      1 | 
|   3 |    VIEW                             |                             |      1 |    347 |      0 |00:00:00.05 |       1 |      1 | 
|   4 |     WINDOW SORT                     |                             |      1 |    347 |      0 |00:00:00.05 |       1 |      1 | 
|   5 |      NESTED LOOPS                   |                             |      1 |    347 |      0 |00:00:00.05 |       1 |      1 | 
|   6 |       NESTED LOOPS                  |                             |      1 |      1 |      0 |00:00:00.05 |       1 |      1 | 
|   7 |        NESTED LOOPS                 |                             |      1 |      1 |      0 |00:00:00.05 |       1 |      1 | 
|   8 |         MERGE JOIN CARTESIAN        |                             |      1 |      1 |      0 |00:00:00.05 |       1 |      1 | 
|   9 |          TABLE ACCESS BY INDEX ROWID| MDW_TASK_CLASS              |      1 |      1 |      0 |00:00:00.05 |       1 |      1 | 
|* 10 |           INDEX SKIP SCAN           | MDW_TASK_CLASS_IDX1         |      1 |      1 |      0 |00:00:00.05 |       1 |      1 | 
|  11 |          BUFFER SORT                |                             |      0 |      1 |      0 |00:00:00.01 |       0 |      0 | 
|* 12 |           INDEX RANGE SCAN          | MDW_BATCH_ENGINE_IDX1       |      0 |      1 |      0 |00:00:00.01 |       0 |      0 | 
|* 13 |         INDEX RANGE SCAN            | MDW_BATCH_ENGINE_GROUP_IDX1 |      0 |      1 |      0 |00:00:00.01 |       0 |      0 | 
|* 14 |        INDEX RANGE SCAN             | MDW_TASK_TYPE_IDX2          |      0 |      1 |      0 |00:00:00.01 |       0 |      0 | 
|* 15 |       INDEX RANGE SCAN              | MDW_TASK_INSTANCE_IDX4      |      0 |   2216 |      0 |00:00:00.01 |       0 |      0 | 
|  16 |    SORT AGGREGATE                   |                             |      0 |      1 |      0 |00:00:00.01 |       0 |      0 | 
|  17 |     NESTED LOOPS                    |                             |      0 |     40 |      0 |00:00:00.01 |       0 |      0 | 
|* 18 |      INDEX RANGE SCAN               | MDW_TASK_TYPE_IDX2          |      0 |      1 |      0 |00:00:00.01 |       0 |      0 | 
|* 19 |      INDEX RANGE SCAN               | MDW_TASK_INSTANCE_IDX4      |      0 |     36 |      0 |00:00:00.01 |       0 |      0 | 
-------------------------------------------------------------------------------------------------------------------------------------- 

Predicate Information (identified by operation id): 
--------------------------------------------------- 
   2 - filter(("INSTANCE_NUMBER"<=0 OR ("CLASS_RANK"<="POLL_REQ"."POLL_MAX_GRAB" AND "INSTANCE_NUMBER"-"CLASS_RANK"->=0))) 
  10 - access("TC"."INSTANCE_NUMBER"<=0) 
       filter("TC"."INSTANCE_NUMBER"<=0) 
  12 - access("BE"."LABEL"='RLX000') 
  13 - access("EG"."BATCH_ENGINE_ID"="BE"."BATCH_ENGINE_ID" AND "TC"."BATCH_GROUP_ID"="EG"."BATCH_GROUP_ID") 

  14 - access("TT"."TASK_CLASS_ID"="TC"."TASK_CLASS_ID") 
       filter("TT"."TASK_CLASS_ID" IS NOT NULL) 
  15 - access("TI"."STATUS"=0 AND "TI"."APP_CODE"="TT"."APP_CODE" AND "TI"."TASK_LABEL"="TT"."TASK_LABEL") 
  18 - access("TT"."TASK_CLASS_ID"=:B1) 
  19 - access("TI"."STATUS"=1 AND "TI"."APP_CODE"="TT"."APP_CODE" AND "TI"."TASK_LABEL"="TT"."TASK_LABEL") 

On passe de 5102 logical reads à 1 logical read. Bien sûr le fait d’ajouter la clause à l’intérieur de la vue a un impact sur la fonction analytique. C’est pour cela que le CBO s’interdit de merger la vue. Il revient au développeur d’estimer l’impact de cette solution.


Conclusion:

Depuis la version 10g, le CBO peut choisir de merger ou pas une vue en se basant sur le coût estimé de chaque plan. Il existe des cas où le CBO décide pour éviter d’impacter le résultat de la requête de ne pas merger la vue bien que le coût du plan aurait été bien plus intéressant (ex : ROWNUM ou fonction analytique impliqués dans la vue). Il existe aussi des cas où la vue n’est pas mergée mais où le CBO décide quand même d’effectuer un push down de certains prédicats à l’intérieur de la vue.

Aucun commentaire:

Enregistrer un commentaire