Mon petit Data Mart (Partie 2) : Vues Matérialisées et Query Rewrite

Si vous avez suivi l’article précédent intitulé « Mon petit Data Mart (Partie 1) : Dimensions, Partitions et Données« , vous disposez maintenant d’une base de données 11g Release 2 avec une table de fait et 4 tables de dimensions. Nous allons maintenant utiliser ce schéma pour illustrer l’utilisation et l’intérêt des vues matérialisées et du query rewrite…

Un calcul de somme

Commençons par une requête simple qui permet de calculer la somme des colonnes metric1 groupées par an. Comme vous pouvez le voir, dans mon cas ci-dessous, le temps d’exécution (en 11.2.0.1 sur mon laptop sous Linux), il faut à peu près 20 secondes pour exécuter la requête :

alter session set query_rewrite_enabled=true;
alter session set query_rewrite_integrity=enforced;
set timing on
set num 12
select trunc(t.month_id,'YYYY') year,
sum(f.metric1) m1
from fact f, time t
where t.time_id=f.time_id
group by trunc(t.month_id,'YYYY')
order by 1;

YEAR M1
--------- ------------
01-JAN-09 10248074782
01-JAN-10 170331336

Elapsed: 00:00:20.01

Vue matérialisée et query rewrite

Pour vous montrer l’intérêt d’une vue matérialisée, vous allons créée une vue (sans toutefois que celle-ci soit rafraichie automatiquement) et nous arranger pour que la requête puisse l’utiliser. Dans notre cas, nous allons simplement aggréger la table FACT au niveau mois de l’axe temps (sans toucher les autres niveaux). Voilà la vue matérialisée associée:

drop materialized view ag_fact_by_month;

create materialized view ag_fact_by_month
partition by range(month_id) interval(NUMTOYMINTERVAL(1,'MONTH'))
subpartition by hash(dim2_id) subpartitions 2
(partition p0 values less than (to_date('01/01/2009','DD/MM/YYYY')))
nologging
enable query rewrite
as select t.month_id,
f.dim2_id,
f.dim3_id1,
f.dim3_id2,
f.dim4_id,
sum(f.metric1) metric1_sum,
count(f.metric1) metric1_count,
avg(f.metric1) metric1_avg,
sum(f.metric2) metric2_sum,
count(f.metric2) metric2_count,
avg(f.metric2) metric2_avg
from fact f, time t
where f.time_id=t.time_id
group by t.month_id, f.dim2_id,
f.dim3_id1, f.dim3_id2, f.dim4_id;

Elapsed: 00:00:35.62

select count(*)
from ag_fact_by_month;

COUNT(*)
--------
15600

Si vous relancez la requête précédente, vous constaterez que les temps d’exécution de la requête sont radicalement transformés et, que le plan d’exécution utilise notre vue matérialisée :

alter session set query_rewrite_enabled=true;
alter session set query_rewrite_integrity=enforced;
set timing on
set num 12
select trunc(t.month_id,'YYYY') year,
sum(f.metric1) m1
from fact f, time t
where t.time_id=f.time_id
group by trunc(t.month_id,'YYYY')
order by 1;

YEAR M1
--------- ------------
01-JAN-09 10248074782
01-JAN-10 170331336

Elapsed: 00:00:00.03


select *
from table(dbms_xplan.display_cursor(
format=>'basic'));

EXPLAINED SQL STATEMENT:
------------------------
select trunc(t.month_id,'YYYY') year, sum(f.metric1) m1 from
fact f, time t where t.time_id=f.time_id group by
trunc(t.month_id,'YYYY') order by 1

Plan hash value: 2405448810

-----------------------------------------------------------
| Id | Operation | Name |
-----------------------------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | SORT ORDER BY | |
| 2 | HASH GROUP BY | |
| 3 | MAT_VIEW REWRITE ACCESS FULL| AG_FACT_BY_MONTH |
-----------------------------------------------------------

Consistence et Query Rewrite

Lorsque vous modifiez les données de la table de faits, la vue matérialisée n’est plus utilisable dans les conditions normales. L’exemple qui suit illustre les 2 méthodes qui permettent d’utiliser la vue matérialisée de nouveau à savoir (1) l’utilisation du paramètre query_rewrite_integrity=stale_tolerated ou (2) le refresh de la vue matérialisée :

insert into fact
values (to_date('06/01/2010','DD/MM/YYYY'),
2, 2, 1, 'AXIS 2',
'XX', 'XS3', 7,
to_date('11/01/2010','DD/MM/YYYY'),
17, 22);

commit;

set timing on
set num 12
select trunc(t.month_id,'YYYY') year,
sum(f.metric1) m1
from fact f, time t
where t.time_id=f.time_id
group by trunc(t.month_id,'YYYY')
order by 1;

YEAR M1
--------- ------------
01-JAN-09 10248074782
01-JAN-10 170331353

Elapsed: 00:00:19.88


alter session set query_rewrite_integrity=stale_tolerated;

select trunc(t.month_id,'YYYY') year,
sum(f.metric1) m1
from fact f, time t
where t.time_id=f.time_id
group by trunc(t.month_id,'YYYY')
order by 1;

YEAR M1
--------- ------------
01-JAN-09 10248074782
01-JAN-10 170331336

Elapsed: 00:00:00.02


exec dbms_mview.refresh('ag_fact_by_month','complete')
alter session set query_rewrite_integrity=enforced;
select trunc(t.month_id,'YYYY') year,
sum(f.metric1) m1
from fact f, time t
where t.time_id=f.time_id
group by trunc(t.month_id,'YYYY')
order by 1;

YEAR M1
--------- ------------
01-JAN-09 10248074782
01-JAN-10 170331353

Elapsed: 00:00:00.02

Et ensuite ?

Si l’utilisation du niveau aggrégé (i.e. time.month_id) passe par la vue matérialisée, il n’en est rien si vous interrogez un autre niveau (e.g. time.year_id), comme vous pouvez vous en rendre compte ci-dessous :

select to_char(t.year_id,'YYYY') year,
sum(f.metric1) m1
from fact f, time t
where t.time_id=f.time_id
group by to_char(t.year_id,'YYYY')
order by 1;

YEAR M1
---- ------------
2009 10248074782
2010 170331353

Elapsed: 00:00:20.94


select *
from table(dbms_xplan.display_cursor(
format=>'basic'));

EXPLAINED SQL STATEMENT:
------------------------
select to_char(t.year_id,'YYYY') year, sum(f.metric1) m1 from
fact f, time t where t.time_id=f.time_id group by
to_char(t.year_id,'YYYY') order by 1

Plan hash value: 2313903028

--------------------------------------
| Id | Operation | Name |
--------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | SORT GROUP BY | |
| 2 | PARTITION RANGE ALL | |
| 3 | HASH JOIN | |
| 4 | TABLE ACCESS FULL | TIME |
| 5 | PARTITION HASH ALL| |
| 6 | TABLE ACCESS FULL| FACT |
--------------------------------------

Dans notre prochain article, nous explorerons l’utilisation des dimensions pour répondre à ce besoin.