samedi 5 mars 2016

ORA-14257: cannot move partition other than a Range, List, System, or Hash

Si lors d'un move de partition vous tombez sur l'erreur suivante "ORA-14257: cannot move partition other than a Range, List, System, or Hash partition", il est fort probable que vous avez essayé de déplacer des partitions qui contiennent des sous partitions. C'est ce qui m'est arrivé récemment lorsque j'ai voulu déplacer les partitions P_MINVAL et P_MAXVAL d'une des tables de mon client dans un autre tablepsace:
SQL> ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE PARTITION P_MAXVAL TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE PARTITION P_MAXVAL TABLESPACE USERS_LC32_DATA01 
                                                          * 
ERROR at line 1: 
ORA-14257: cannot move partition other than a Range, List, System, or Hash 
partition
Je me suis alors rendu compte que cette table avait des sous partitions et j'ai donc généré grâce à la requête suivante les commandes qui vont faire le move de toutes les sous partitions de la table CLIENT_DATA_DETAILS_PART:
select 'ALTER TABLE FORCE.' || TABLE_NAME || ' MOVE SUBPARTITION ' || SUBPARTITION_NAME || ' TABLESPACE USERS_LC32_DATA01;' 
FROM dba_tab_subpartitions 
where table_owner = 'FORCE' 
and table_name = ('CLIENT_DATA_DETAILS_PART') 
and partition_name in ('P_MINVAL', 'P_MAXVAL'); 
 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MAXVAL_ASIA TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MAXVAL_US TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MAXVAL_EUROPE TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MAXVAL_EUROPE_4 TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MAXVAL_EUROPE_5 TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MAXVAL_EUROPE_6 TABLESPACE USERS_LC32_DATA01; 
............................... 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MINVAL_EUROPE_15 TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MINVAL_EUROPE_16 TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MINVAL_EUROPE_17 TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MINVAL_EUROPE_18 TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MINVAL_EUROPE_19 TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MINVAL_EUROPE_20 TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MINVAL_EUROPE_21 TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MINVAL_EUROPE_22 TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MINVAL_EUROPE_23 TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MINVAL_EUROPE_24 TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MINVAL_EUROPE_25 TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MINVAL_EUROPE_26 TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MINVAL_EUROPE_27 TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MINVAL_EUROPE_28 TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MINVAL_EUROPE_29 TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MINVAL_EUROPE_30 TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE SUBPARTITION P_MINVAL_OTHER TABLESPACE USERS_LC32_DATA01; 
Si mes sous partitions se trouvent désormais dans le tablespace USERS_LC32_DATA01 comme je le souhaitais j'ai toujours les partitions mères de ces sous partitions qui pointent dans le mauvais tablespace et bien sûr il est toujours impossible de faire un move de ces partitions:
SQL> select distinct tablespace_name 
  2  FROM dba_tab_subpartitions 
  3  where table_owner = 'FORCE' 
  4  and table_name = ('CLIENT_DATA_DETAILS_PART') 
  5  and partition_name in ('P_MINVAL', 'P_MAXVAL'); 
 
TABLESPACE_NAME 
------------------------------ 
USERS_LC32_DATA01 
 
SQL> select tablespace_name 
  2  FROM dba_tab_partitions 
  3  WHERE table_owner = 'FORCE' AND table_name in ('CLIENT_DATA_DETAILS_PART') 
  4   AND partition_name in ('P_MINVAL','P_MAXVAL'); 
 
TABLESPACE_NAME 
------------------------------ 
FORCE_HP32_CURRENT1 
FORCE_HP32_CURRENT1 
 
SQL> ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE PARTITION P_MAXVAL TABLESPACE USERS_LC32_DATA01; 
ALTER TABLE FORCE.CLIENT_DATA_DETAILS_PART MOVE PARTITION P_MAXVAL TABLESPACE USERS_LC32_DATA01 
                                                          * 
ERROR at line 1: 
ORA-14257: cannot move partition other than a Range, List, System, or Hash 
partition 
Je souhaiterais pourtant que ces partitions pointent sur le tablespace USERS_LC32_DATA01 et non pas FORCE_HP32_CURRENT1.
La solution consiste simplement à modifier le DEFAULT ATTRIBUTES pour ces partitions:
alter table FORCE.CLIENT_DATA_DETAILS_PART modify default attributes for partition P_MINVAL tablespace USERS_LC32_DATA01; 
alter table FORCE.CLIENT_DATA_DETAILS_PART modify default attributes for partition P_MAXVAL tablespace USERS_LC32_DATA01; 
 
SQL> alter table FORCE.CLIENT_DATA_DETAILS_PART modify default attributes for partition P_MAXVAL tablespace USERS_LC32_DATA01; 
 
Table altered. 
 
SQL> alter table FORCE.CLIENT_DATA_DETAILS_PART modify default attributes for partition P_MINVAL tablespace USERS_LC32_DATA01; 
 
Table altered. 
    
SQL> select tablespace_name 
  2  FROM dba_tab_partitions 
  3  WHERE table_owner = 'FORCE' AND table_name in ('CLIENT_DATA_DETAILS_PART') 
  4   AND partition_name in ('P_MINVAL','P_MAXVAL'); 
 
TABLESPACE_NAME 
------------------------------ 
USERS_LC32_DATA01 
USERS_LC32_DATA01
Comme vous pouvez le constater, mes partitions pointent désormais bien sur le nouveau tablespace USERS_LC32_DATA01.

lundi 1 février 2016

Les histogrammes (3): Height-Balanced

Dans l’article précédent nous avions vu qu’avec un histogramme de type FREQUENCY on avait un bucket alloué pour chaque valeur distincte ce qui permettait au CBO d’estimer une cardinalité juste. Cependant, le nombre de buckets étant limité à 254 il n’est plus possible d’avoir un histogramme FREQUENCY pour les colonnes ayant un nombre de valeurs distinctes supérieur à cette limite. Oracle calcule à la place des histogrammes de type height-balanced (HB).

La colonne C_HB de ma table T1 contenant 829 valeurs distinctes est candidate pour un histogramme height-balanced.
exec dbms_stats.gather_table_stats(user,'T1', method_opt=>'for columns C_HB size 829');

ERROR at line 1:
ORA-20000: Cannot parse for clause: for columns C_HB size 829
ORA-06512: at "SYS.DBMS_STATS", line 24281
ORA-06512: at "SYS.DBMS_STATS", line 24332
ORA-06512: at line 1
L’erreur précédente nous montre qu’en 11g, il est inutile de positionner un nombre de buckets égale au nombre de valeurs distinctes lorsque ce dernier est supérieur à 254. Il faut donc calculer les statistiques pour cette colonne avec 254 buckets :
 exec dbms_stats.gather_table_stats(user,'T1', method_opt=>'for columns C_HB size 254'); 
 
select column_name,num_distinct,density,num_nulls,num_buckets,sample_size,histogram  
from user_tab_col_statistics  
where table_name='T1' and column_name='C_HB'; 
                                                                                                                                                                                        
COLUMN_NAME                    NUM_DISTINCT    DENSITY  NUM_NULLS NUM_BUCKETS SAMPLE_SIZE HISTOGRAM 
------------------------------ ------------ ---------- ---------- ----------- ----------- --------------- 
C_HB                                    829 ,000748974          0         254       14739 HEIGHT BALANCED
La colonne HISTOGRAM de la vue USER_TAB_COL_STATISTICS nous montre que c’est bel et bien un histogramme height-balanced qui a été calculé.
Jetons un œil aux 10 valeurs les plus récurrentes dans la table :
 select * from ( 
SELECT C_HB, count(*) AS frequency, trunc(ratio_to_report(count(*)) OVER ()*100,2) AS percent 
 FROM t1 
 GROUP BY C_HB 
 ORDER BY 3 desc 
 ) 
 where rownum<=10 
 ; 
  
      C_HB  FREQUENCY    PERCENT 
--------- ---------- ---------- 
      999       5710      38,74 
        0       4601      31,21 
        4       1853      12,57 
        5        237        1,6 
        8        195       1,32 
       19        169       1,14 
        1        152       1,03 
       16         84        ,56 
       13         70        ,47 
        6         47        ,31
On s’aperçoit que la valeur 999 qui est la valeur la plus présente apparait 5710 fois, la valeur 0 apparait 4601 fois et la valeur 4 apparait 1853 fois. A elles trois ces valeurs représentent plus de 80% des données.


Principe de calcul des histogrammes height-balanced
Pour calculer l’histogramme sur la colonne C_HB, Oracle commence par trier les valeurs de la colonne C_HB puis il divise ces données triées en buckets de 58 valeurs (num_rows/num_buckets=14739/254=58). Pour chaque bucket Oracle conserve la valeur la plus grande, c’est ce qu’on appelle le ENDPOINT_VALUE. La première valeur triée est la valeur 0 qui apparait 4601 fois. La taille du bucket étant de 58, la valeur 0 correspondra au ENDPOINT_VALUE de 79 buckets (4601/58=79). Ensuite, Oracle arrive à la valeur 1 qui apparait 152 fois dans la table et donc cette valeur apparaitra comme ENDPOINT_VALUE dans 2 buckets (152/58=2.6). En continuant avec ce mécanisme on aura dans notre histogramme 254 valeurs et on comprend qu’en fonction de l'évolution des données dans la table on peut avoir un ENDPOINT_VALUE qui diffère après chaque exécution du calcul de statistiques. Le fameux problème d’instabilité des histogrammes height-balanced vient de là.

Requêtons maintenant la vue USER_TAB_HISTOGRAMS pour la colonne C_HB et observons ce qu'on obtient dans notre histogramme :

select ENDPOINT_NUMBER, ENDPOINT_VALUE 
from USER_TAB_HISTOGRAMS 
where table_name = 'T1' and column_name = 'C_HB' 
order by ENDPOINT_NUMBER; 
 
ENDPOINT_NUMBER ENDPOINT_VALUE 
--------------- -------------- 
             79              0 
             81              1 
             82              2 
            114              4 
            118              5 
            119              6 
            122              8 
            123              9 
            124             12 
            125             13 
            127             16 
            130             19 
            131             21 
            132             25 
            133             32 
            134             40 
            135             53 
            136             70 
            137             88 
            138            110 
            139            138 
            140            216 
            141            256 
            142            370 
            143            460 
            144            640 
            243            999 
            244           1414 
            245           2150 
            246           3333 
            247           5484 
            248           8038 
            249          14660 
            250          27455 
            251          64093 
            252         187520 
            253         986698 
            254       63681020 
 
38 rows selected. 
Là vous vous demandez peut-être pourquoi nous n’obtenons que 38 lignes dans notre histogramme au lieu de 254 ? Effectivement, bien que la règle consiste à avoir une valeur pour chaque bucket, la colonne ENDPOINT_NUMBER étant une valeur cumulative, Oracle ne conservera que le dernier ENDPOINT_NUMBER lorsque plusieurs ENDPOINT_VALUE se succèdent avec la même valeur. Ainsi, on peut déduire en regardant notre histogramme que la fréquence de la valeur 0 est de 79 car il n’existe pas de ENDPOINT_NUMBER avant ce nombre.
La valeur 1 qui apparait au ENDPOINT_NUMBER 81 juste après le 79 a une fréquence de 2 (81-79), en d’autres termes on peut dire que la valeur 1 apparait à deux reprises comme endpoint_value d’un bucket.

La valeur 4 apparait au ENDPOINT_NUMBER de 114 après la valeur 2 qui apparait au endpoint 82. Cette ligne dans la vue USER_TAB_HISTOGRAMS nous permet de déduire 2 choses :
  • La valeur 3 n’est pas présente dans l’histogramme (même si la valeur apparait 9 fois dans la table)
  • La valeur 4 a une fréquence de 32 (114-82)
La valeur 999 qui est la valeur la plus présente dans la table apparait au ENDPOINT_NUMBER 243 juste après le ENDPOINT_NUMBER 144 ce qui nous permet d’en déduire sa fréquence: 243-144=99.

Pour afficher les fréquences de chaque valeur capturée on peut utiliser la requête suivante qui utilise notamment la fonction LAG:
select endpoint_value as column_value, 
endpoint_number as cummulative_frequency, 
endpoint_number - lag(endpoint_number,1,0) over (order by endpoint_number) as frequency 
from user_tab_histograms 
where table_name = 'T1' and column_name = 'C_HB'; 
 
COLUMN_VALUE CUMMULATIVE_FREQUENCY  FREQUENCY

------------ --------------------- ----------

           0                    79         79

           1                    81          2

           2                    82          1

           4                   114         32

           5                   118          4

           6                   119          1

           8                   122          3

           9                   123          1

          12                   124          1

          13                   125          1

          16                   127          2

          19                   130          3

          21                   131          1

          25                   132          1

          32                   133          1

          40                   134          1

          53                   135          1

          70                   136          1

          88                   137          1

         110                   138          1

         138                   139          1

         216                   140          1

         256                   141          1

         370                   142          1

         460                   143          1

         640                   144          1

         999                   243         99

        1414                   244          1

        2150                   245          1

        3333                   246          1

        5484                   247          1

        8038                   248          1

       14660                   249          1

       27455                   250          1

       64093                   251          1

      187520                   252          1

      986698                   253          1

    63681020                   254          1


Histogrammes Height-Balanced et cardinalités :

Dans l’article sur les histogrammes FREQUENCY j’avais écrit qu’à partir du moment où l’on avait autant de buckets que de valeurs distinctes, on pouvait considérer toutes les valeurs de l'histogramme comme populaires ce qui permettait au CBO d’estimer une bonne cardinalité. Malheureusement, il n’en va pas de même pour les histogrammes height-balanced car seules les valeurs ayant une fréquence supérieure à 1 sont considérées comme populaires. Les valeurs apparaissant une seule fois ou bien celles n’ayant pas été capturées lors du calcul de statistiques sont donc considérées comme non populaires. De plus, puisqu’il est nécessaire qu’une valeur apparaisse au moins 2 fois comme endpoint_value pour devenir populaire on peut en déduire qu’Oracle n’est capable de capturer au mieux que 127 valeurs populaires.

Voyons ce que donnent les estimations du CBO lorsqu’on requête une valeur populaire :
-- popular value 
alter system flush shared_pool; 
select * from T1 where C_HB=999; 
 
------------------------------------------------------------------------------------ 
| Id  | Operation         | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers | 
------------------------------------------------------------------------------------ 
|   0 | SELECT STATEMENT  |      |      1 |        |   5710 |00:00:00.01 |     415 | 
|*  1 |  TABLE ACCESS FULL| T1   |      1 |   5745 |   5710 |00:00:00.01 |     415 | 
------------------------------------------------------------------------------------ 
On constate que la cardinalité estimée est très proche de la réalité et que le CBO a choisi de faire un Full Table Scan en ne générant que 415 logical reads. Pour rappel, voici le plan obtenu lorsque le CBO ne disposait d’aucun histogramme pour la colonne C_HB. L’optimiseur avait sous-estimé la cardinalité retournée et avait opté pour un accès indexé générant 806 logical I/Os :
select * from T1 where C_HB=999; 
 
------------------------------------------------------------------------------------------------ 
| Id  | Operation                   | Name   | Starts | E-Rows | A-Rows |   A-Time   | Buffers | 
------------------------------------------------------------------------------------------------ 
|   0 | SELECT STATEMENT            |        |      1 |        |   5710 |00:00:00.01 |     806 | 
|   1 |  TABLE ACCESS BY INDEX ROWID| T1     |      1 |     18 |   5710 |00:00:00.01 |     806 | 
|*  2 |   INDEX RANGE SCAN          | IDX_HB |      1 |     18 |   5710 |00:00:00.01 |     394 | 
------------------------------------------------------------------------------------------------ 
Voyons maintenant ce que donnent les estimations du CBO pour une valeur non populaire mais présente dans l’histogramme:
-- Non popular presente dans l'histogramme 
select * from T1 where C_HB=256; 
 ------------------------------------------------------------------------------------------------ 
| Id  | Operation                   | Name   | Starts | E-Rows | A-Rows |   A-Time   | Buffers | 
------------------------------------------------------------------------------------------------ 
|   0 | SELECT STATEMENT            |        |      1 |        |     24 |00:00:00.01 |      17 | 
|   1 |  TABLE ACCESS BY INDEX ROWID| T1     |      1 |      2 |     24 |00:00:00.01 |      17 | 
|*  2 |   INDEX RANGE SCAN          | IDX_HB |      1 |      2 |     24 |00:00:00.01 |       4 | 
------------------------------------------------------------------------------------------------
Cette fois à l’inverse de la valeur popular le CBO se trompe dans son estimation : 2 lignes estimées contre 24 réellement.

Regardons enfin ce qu’on obtient lorsqu’on utilise une valeur non populaire et n’ayant pas été capturée par le calcul de statistiques :
 -- Non popular non capturée 
select * from T1 where C_HB=3; 
 
------------------------------------------------------------------------------------------------ 
| Id  | Operation                   | Name   | Starts | E-Rows | A-Rows |   A-Time   | Buffers | 
------------------------------------------------------------------------------------------------ 
|   0 | SELECT STATEMENT            |        |      1 |        |      9 |00:00:00.01 |       7 | 
|   1 |  TABLE ACCESS BY INDEX ROWID| T1     |      1 |      2 |      9 |00:00:00.01 |       7 | 
|*  2 |   INDEX RANGE SCAN          | IDX_HB |      1 |      2 |      9 |00:00:00.01 |       3 | 
------------------------------------------------------------------------------------------------
Là encore le CBO ne peut estimer une cardinalité juste et c’est là où le bât blesse avec les histogrammes height-balanced : le CBO n’est capable d’estimer le bon nombre de lignes retournées si et seulement si la valeur requêtée est une valeur populaire, or comme nous l’avons dit précédemment Oracle n’est capable de capturer au mieux que 127 valeurs populaires. Imaginons une table contenant plusieurs millions de valeurs distinctes avec un histogramme height-balanced, le CBO ne sera capable d’estimer une cardinalité fiable que lorsqu’une valeur populaire sera requêtée (c’est-à-dire 0.001% des valeurs dans ce cas). Si cette table faisait l’objet d’une requête très complexe avec beaucoup de jointures le plan d’exécution choisi par le CBO pourrait s’avérer catastrophique.
Pour ceux que ça intéresse je donne ci-dessous les formules appliquées par le CBO pour calculer les cardinalités selon les cas :

Cas d’une valeur populaire :

BUCKET_SIZE = SAMPLE_SIZE / NUM_BUCKETS

BUCKET_SIZE = 14739/254 = 58.027

Cardinalité estimée = FREQUENCY * BUCKET_SIZE

Cardinalité estimée = 99 * 58.027 = 5744.72 ~= 5745 => correspond bien à ce qu’on voit dans le plan pour la colonne E-Rows

Cas d’une valeur NON populaire (capturée ou pas) :

Cardinalité estimée = NUM_ROWS * NewDensity

NewDensity est une fonction interne à Oracle dont le nom apparait dans la 10053 et qui lui permet de faire une estimation en cas de valeurs non populaires pour les colonnes ayant un histogramme de type height-balanced.



CONCLUSION :

La notion d’histogrammes height-balanced est une notion plus complexe à assimiler que celle des histogrammes FREQUENCY néanmoins voici les points qu'il me semble essentiel de retenir :
  • Oracle est dans l’incapacité de calculer des histogrammes de type frequency et calcule donc des histogrammes height-balanced dès lors que le nombre de buckets est inférieur au nombre de valeurs distinctes ou dès lors que le nombre de valeurs distinctes est supérieur à 254 (car le nombre de buckets est limité à 254).
  • Dans les histogrammes height-balanced la colonne ENDPOINT_NUMBER correspond à l’id du bucket et la colonne ENDPOINT_VALUE correspond à la plus grande valeur du bucket.
  • Lorsque le ENDPOINT_VALUE est identique sur plusieurs buckets, Oracle ne stocke que le dernier bucket.
  • Oracle n’est capable de capturer au mieux que 127 valeurs populaires
  • Oracle est capable d’estimer une bonne cardinalité uniquement pour les valeurs populaires
  • Puisque seule la plus grande valeur de chaque bucket est prise en compte une valeur peut devenir populaire un jour puis non populaire un autre jour en fonction du fait qu’elle ait été capturée par le processus de calcul de statistiques ou pas. C’est ce qui rend les histogrammes HB très versatiles et qui en font une des premières sources d’instabilité dans les plans d’exécution.

Nous verrons dans un prochain article comment, avec la 12c et les histogrammes TOP-frequency et Hybrides, Oracle a tenté d’améliorer ses histogrammes.

dimanche 20 décembre 2015

Les histogrammes (2): Frequency


Dans l’article précédent nous avons vu que la cardinalité pour la colonne C_FREQ était mal estimée par le CBO car il lui manquait l’information sur la distribution des valeurs de cette colonne :
select * from T1 where C_FREQ=3; 
 
------------------------------------------------------------------------------------ 
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | 
------------------------------------------------------------------------------------ 
| 0 | SELECT STATEMENT | | 1 | | 10 |00:00:00.01 | 35 | 
|* 1 | TABLE ACCESS FULL| T1 | 1 | 4913 | 10 |00:00:00.01 | 35 | 
------------------------------------------------------------------------------------

Cette information peut être apportée en calculant un histogramme sur la colonne. Jusqu’à la version 11G on avait 2 types d’histogrammes : FREQUENCY et HEIGHT-BALANCED. Les histogrammes de type FREQUENCY vont stocker le nombre de lignes (la fréquence) de chaque valeur ce qui permet d’obtenir des cardinalités très fiables. Cependant ces histogrammes sont limités à des colonnes ayant moins de 254 valeurs distinctes.

Voici comment on calcule des statistiques avec histogrammes pour la colonne C_FREQ :

exec dbms_stats.gather_table_stats(user,'T1', method_opt=>'for columns C_FREQ size 3');

Le chiffre 3 indique que je souhaite utiliser 3 buckets puisqu’on a que 3 valeurs distinctes. Même si j'utilisais une valeur supérieure Oracle ne prendrait en compte que 3 buckets pour cette colonne. 
On peut vérifier qu’un histogramme de type FREQUENCY a bien été calculé en questionnant la table USER_TAB_COL_STATISTICS :
select column_name,num_distinct,density,num_nulls,num_buckets,sample_size,histogram  
from user_tab_col_statistics  
where table_name='T1' and column_name='C_FREQ'; 
 
COLUMN_NAME                    NUM_DISTINCT    DENSITY  NUM_NULLS NUM_BUCKETS SAMPLE_SIZE HISTOGRAM 
------------------------------ ------------ ---------- ---------- ----------- ----------- ---------- 
C_FREQ                                    3 ,000033924          0           3       14739 FREQUENCY


On peut également visualiser le contenu de l’histogramme en requêtant la vue USER_TAB_HISTOGRAMS : 
select endpoint_value as column_value, 
endpoint_number as cummulative_frequency, 
endpoint_number - lag(endpoint_number,1,0) over (order by endpoint_number) as frequency 
from user_tab_histograms 
where table_name = 'T1' and column_name = 'C_FREQ'; 
 
COLUMN_VALUE CUMMULATIVE_FREQUENCY  FREQUENCY 
------------ --------------------- ---------- 
           1                 13257      13257 
           2                 14729       1472 
           3                 14739         10

La colonne ENDPOINT_VALUE correspond à la valeur de la colonne et la colonne ENDPOINT_NUMBER correspond au nombre cumulé de lignes. Ainsi, pour obtenir le nombre de lignes (la fréquence) pour la valeur 2 il suffit de soustraire au ENDPOINT_NUMBER de la valeur 2 le ENDPOINT_NUMBER de la valeur 1 (14729-13257=1472), d’où l’utilisation de la fonction LAG dans la requête ci-dessus.
On avait vu dans l’article précédent que sans histogrammes le CBO n’arrivait pas à estimer la cardinalité pour C_FREQ=3:
select * from T1 where C_FREQ=3; 
 
------------------------------------------------------------------------------------ 
| Id  | Operation         | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers | 
------------------------------------------------------------------------------------ 
|   0 | SELECT STATEMENT  |      |      1 |        |     10 |00:00:00.01 |      35 | 
|*  1 |  TABLE ACCESS FULL| T1   |      1 |   4913 |     10 |00:00:00.01 |      35 | 
------------------------------------------------------------------------------------

Voyons donc maintenant ce qu’estime le CBO avec l’histogramme FREQUENCY en place pour cette colonne :
alter system flush shared_pool; 
select * from T1 where C_FREQ=3; 
 
-------------------------------------------------------------------------------------------------- 
| Id  | Operation                   | Name     | Starts | E-Rows | A-Rows |   A-Time   | Buffers | 
-------------------------------------------------------------------------------------------------- 
|   0 | SELECT STATEMENT            |          |      1 |        |     10 |00:00:00.01 |       5 | 
|   1 |  TABLE ACCESS BY INDEX ROWID| T1       |      1 |     10 |     10 |00:00:00.01 |       5 | 
|*  2 |   INDEX RANGE SCAN          | IDX_FREQ |      1 |     10 |     10 |00:00:00.01 |       3 | 
--------------------------------------------------------------------------------------------------

L’estimation match cette fois parfaitement la réalité (E-Rows=A-Rows=10). Du coup, le CBO opte pour un accès indexé et le nombre de logical reads est divisé par 5.

Cet exemple montre bien qu’un histogramme Frequency permet d’obtenir une cardinalité égale ou proche de la réalité. Cette justesse au niveau de l’estimation vient du fait que pour les histogrammes FREQUENCY il existe autant de buckets que de valeurs distinctes, ainsi, toutes les valeurs présentes dans la table sont populaires à condition bien sûr qu’un calcul de statistiques ait eu lieu au moment où cette distribution non uniforme des données était présente. En effet, imaginons une nouvelle ligne insérée dans la table T1 avec C_FREQ = 4, la valeur 4 n’étant pas présente dans l’histogramme FREQUENCY le CBO ne peut estimer correctement la cardinalité pour cette valeur. Il est donc primordial d’avoir un calcul de statistiques qui tourne toujours au juste moment.

Dans le prochain article nous verrons comment Oracle calcule les histogrammes HEIGHT-BALANCED pour répondre aux problématiques des colonnes de plus 254 valeurs distinctes.

lundi 30 novembre 2015

Les histogrammes (1): Introduction

J’ai remarqué que le sujet des histogrammes était souvent très mal assimilé même chez les DBAs et les développeurs les plus expérimentés alors qu’ils constituent un concept fondamental en matière de Tuning SQL.

Je vais tenter à travers une série d’articles de vous présenter ce qu’il est nécessaire de savoir sur le sujet : à quoi servent les histogrammes ? Comment on les calcule ? Quels sont les différents types d’histogrammes ? Comment sont-ils stockés en base ? Quels sont leurs avantages et inconvénients ?

Dans un premier temps je vais me limiter à la version 11g puis nous verrons dans un futur article que la 12c a apporté pas mal de changements. 

 Exemple:

create table T1 as  
select rownum id, decode(mod(rownum,10),0,2,1) c_freq, nvl(blocks,999) c_hb  
from dba_tables ; 
 
update T1 set c_freq=3 where rownum<=10; 
commit; 
 
create index idx_freq on T1(C_FREQ); 
create index idx_hb on T1(C_HB); 
 
select c_freq,count(*) from T1 group by c_freq order by 2 desc; 
 
    C_FREQ   COUNT(*) 
---------- ---------- 
         1      13257 
         2       1472 
         3         10 
 
          
select c_hb,count(*) from T1 group by c_hb order by 2 desc; 
 
      C_HB   COUNT(*) 
---------- ---------- 
       999       5710 
         0       4601 
         4       1853 
         5        237 
         8        195 
        19        169 
         1        152 
        16         84 
        13         70 
         6         47 
        32         34 
         7         29 
        20         26 
       256         24 
         9         23 
     .......................... 
      5046          1 
       
829 rows selected.

Notre exemple est basé sur une table de 14 739 lignes ayant 3 colonnes : une colonne ID qui est unique, une colonne C_FREQ possédant 3 valeurs distinctes et une colonne C_HB contenant 829 valeurs distinctes. On a également un index sur la colonne C_FREQ et un autre sur la colonne C_HB. Les deux requêtes de comptage mettent en évidence que la distribution des données des deux colonnes n’est pas répartie de manière uniforme, c’est-à-dire que certaines valeurs se répètent beaucoup plus que d’autres. Ainsi, selon la popularité de la valeur de la colonne utilisée dans la requête on utilisera soit un accès indexé soit un Full Scan de la table.
 
Estimations du CBO sans histogrammes
Dans un premier temps on collecte des statistiques pour la table T1 et ses colonnes mais sans calculer d’histogrammes :

exec dbms_stats.gather_table_stats (user, 'T1', method_opt=>'for all columns size 1'); 
 
select num_rows from user_tables where table_name='T1'; 
 
  NUM_ROWS 
---------- 
     14739 
 
select column_name,num_distinct,density,num_nulls,num_buckets,sample_size,histogram  
from user_tab_col_statistics  
where table_name='T1' and column_name='C_FREQ'; 
 
COLUMN_NAME                    NUM_DISTINCT    DENSITY  NUM_NULLS NUM_BUCKETS SAMPLE_SIZE HISTOGRAM 
------------------------------ ------------ ---------- ---------- ----------- ----------- ----------- 
C_FREQ                                    3 ,333333333          0           1       14739 NONE
Le fait d’avoir écrit « FOR ALL COLUMNS SIZE 1 » pour le paramètre METHOD_OPT indique qu’on veut calculer des statistiques pour toutes les colonnes mais sans histogrammes (c’est ce que signifie le SIZE 1). La colonne HISTOGRAM de la vue USER_TAB_COL_STATISTICS est à NONE ce qui confirme bien qu’aucun histogramme n’a été calculé pour la colonne C_FREQ de la table T1.
Voyons maintenant ce qu’estime le CBO lorsqu’on requête les lignes de la table T1 avec C_FREQ = 3 :

select * from T1 where C_FREQ=3; 
 
------------------------------------------------------------------------------------ 
| Id  | Operation         | Name | Starts | E-Rows | A-Rows |   A-Time   | Buffers | 
------------------------------------------------------------------------------------ 
|   0 | SELECT STATEMENT  |      |      1 |        |     10 |00:00:00.01 |      35 | 
|*  1 |  TABLE ACCESS FULL| T1   |      1 |   4913 |     10 |00:00:00.01 |      35 | 
------------------------------------------------------------------------------------
En comparant les colonnes E-Rows et A-Rows du plan on s’aperçoit que le CBO s'est trompé puisqu’il a estimé une cardinalité de 4913 lignes au lieu de 10 réellement retournées. Cette erreur (qui n’en est pas vraiment une) s’explique par le fait qu’en l'absence d'histogrammes le CBO suppose que les lignes sont uniformément réparties dans la table et il applique donc la formule suivante pour estimer la cardinalité retournée:
NUM_ROWS/NUM_DISTINCT = 14739/3 = 4913
A cause du nombre important de lignes estimées, le CBO a opté pour un FULL TABLE SCAN alors qu’un accès via l’index aurait été plus efficace.
Voyons maintenant ce qu’estime le CBO lorsqu’on filtre sur la colonne C_HB qui contient un nombre bien plus important de valeurs distinctes que la colonne C_FREQ :
select column_name,num_distinct,density,num_nulls,num_buckets,sample_size,histogram  
from user_tab_col_statistics  
where table_name='T1' and column_name='C_HB'; 
 
COLUMN_NAME                    NUM_DISTINCT    DENSITY  NUM_NULLS NUM_BUCKETS SAMPLE_SIZE HISTOGRAM 
------------------------------ ------------ ---------- ---------- ----------- ----------- ---------- 
C_HB                                    829 ,001206273          0           1       14739 NONE 
 
select * from T1 where C_HB=999; 
 
------------------------------------------------------------------------------------------------ 
| Id  | Operation                   | Name   | Starts | E-Rows | A-Rows |   A-Time   | Buffers | 
------------------------------------------------------------------------------------------------ 
|   0 | SELECT STATEMENT            |        |      1 |        |   5710 |00:00:00.01 |     806 | 
|   1 |  TABLE ACCESS BY INDEX ROWID| T1     |      1 |     18 |   5710 |00:00:00.01 |     806 | 
|*  2 |   INDEX RANGE SCAN          | IDX_HB |      1 |     18 |   5710 |00:00:00.01 |     394 | 
------------------------------------------------------------------------------------------------
Là aussi le nombre de lignes estimées est loin de la réalité (18 vs 5710). Le nombre 18 a été obtenu en divisant le nombre de lignes de la table par le nombre de valeurs distinctes de la colonne (E-Rows = 14739/829 = 17.79 = 18). Du coup le CBO a choisi un accès indexé alors qu’un Full Table Scan aurait sûrement été plus judicieux.
On se rend donc compte avec ces exemples que lorsque les données sont biaisées en termes de distribution de valeurs, les statistiques classiques ne suffisent plus à l’optimiseur pour estimer des cardinalités proches de la réalité. C’est là que les histogrammes rentrent en jeu.
Nous allons voir dans les prochains articles comment calculer des histogrammes sur les deux colonnes en question et analyser ce que donnent les estimations du CBO grâce à ces nouvelles données.

lundi 28 septembre 2015

Calcul de statistiques et mode concurrent


Beaucoup d’entre vous utilisent surement le paramètre DEGREE dans les procédures du package DBMS_STATS afin de paralléliser leur traitement de calcul de statistiques. Mais ce que beaucoup ignorent c’est que le parallélisme ne se fait qu’au niveau du segment. Il ne peut y avoir un calcul de statistiques sur plusieurs segments à la fois. Donc, le parallélisme appliqué au calcul de statistiques n’est intéressant que lorsqu’on a affaire à de très grosses tables. Heureusement, depuis la 11.2.0.2 il est possible d’avoir un calcul de statistiques pouvant s’effectuer sur plusieurs tables et partitions en même temps, c’est ce qu’on appelle le mode CONCURRENT. Ce mode permet d’accélérer formidablement le calcul de statistiques en profitant pleinement des ressources de la machine d’autant plus qu’on peut le combiner avec le mode parallel.

J’ai récemment pu améliorer le temps de calcul de statistiques d’une des bases de mon client grâce au mode concurrent. Pour se rendre compte de l’amélioration apportée voyons d’abord ce que donne le calcul de statistiques effectué sans le mode concurrent sur un schéma de 800GB et dont les plus grosses tables font plusieurs dizaine de GB.

Test du calcul de stats séquentiel :

     
SQL> select sum(bytes)/1024/1024/1024 from dba_segments where owner='SCHEMA1';

SUM(BYTES)/1024/1024/1024

-------------------------

               801,098389

exec dbms_stats.gather_schema_stats(ownname => 'SCHEMA1', method_opt => 'for all columns size 1');

PL/SQL procedure successfully completed.

Elapsed: 12:55:42.16
Le calcul de statistiques sur le schéma a mis un peu de moins de 13 heures.

Pendant que le calcul de statistiques tournait j’ai lancé plusieurs requêtes à des moments différents pour voir les sessions actives sur ma base et on constate bien que le calcul de statistiques se faisait en mode séquentiel car seule une session était en train de travailler:
SQL> @sql_cur_stmt

  SID    SERIAL# PROG       ADDRESS          HASH_VALUE SQL_ID         CHILD PLAN_HASH_VALUE      EXECS   AVG_ETIME EVENT                SQL_TEXT

----- ---------- ---------- ---------------- ---------- ------------- ------ --------------- ---------- ----------- -------------------- -----------------------------------------

    2      45373 sqlplus.ex 0000002D356943A8 3121805777 85j1xbkx15yfj      0       226853730          1      692.32 db file sequential r select /*+  no_parallel_index(t, "HISCRN1

Test du calcul de stats en parallel :


J’ai ensuite testé le calcul de stats en mode parallel avec un DEGREE de 16
exec dbms_stats.gather_schema_stats(ownname => 'SCHEMA1', degree=> 16, method_opt => 'for all columns size 1');

Elapsed: 11:15:20.68
Cette fois le calcul de statistiques a mis un peu plus de 11 heures. C’est mieux que le mode séquentiel mais ça reste néanmoins toujours trop long. Pendant que le calcul de statistiques en mode parallèle tournait j’ai également lancé quelques requêtes pour voir ce que j’avais dans mes sessions actives :
   SID    SERIAL# PROG       ADDRESS          HASH_VALUE SQL_ID         CHILD PLAN_HASH_VALUE      EXECS   AVG_ETIME EVENT                SQL_TEXT

----- ---------- ---------- ---------------- ---------- ------------- ------ --------------- ---------- ----------- -------------------- -----------------------------------------

    5      12814 oracle@eqd 0000002DDAA90A90 3664547584 9k9zph3d6t3s0      0      2617663258          1       50.30 db file scattered re /* SQL Analyze(0) */ select /*+  full(t)

 1163      42516 oracle@eqd 0000002DDAA90A90 3664547584 9k9zph3d6t3s0      0      2617663258          1       50.30 db file scattered re /* SQL Analyze(0) */ select /*+  full(t)

  235      40059 oracle@eqd 0000002DDAA90A90 3664547584 9k9zph3d6t3s0      0      2617663258          1       50.30 db file scattered re /* SQL Analyze(0) */ select /*+  full(t)

  237      46728 oracle@eqd 0000002DDAA90A90 3664547584 9k9zph3d6t3s0      0      2617663258          1       50.30 db file scattered re /* SQL Analyze(0) */ select /*+  full(t)

  314      18466 oracle@eqd 0000002DDAA90A90 3664547584 9k9zph3d6t3s0      0      2617663258          1       50.30 db file scattered re /* SQL Analyze(0) */ select /*+  full(t)

  391      30356 oracle@eqd 0000002DDAA90A90 3664547584 9k9zph3d6t3s0      0      2617663258          1       50.30 db file scattered re /* SQL Analyze(0) */ select /*+  full(t)

  468      20257 oracle@eqd 0000002DDAA90A90 3664547584 9k9zph3d6t3s0      0      2617663258          1       50.30 db file scattered re /* SQL Analyze(0) */ select /*+  full(t)

  540      28533 sqlplus.ex 0000002DDAA90A90 3664547584 9k9zph3d6t3s0      0      2617663258          1       50.30 PX Deq: Execute Repl /* SQL Analyze(0) */ select /*+  full(t)

  547      15123 oracle@eqd 0000002DDAA90A90 3664547584 9k9zph3d6t3s0      0      2617663258          1       50.30 db file scattered re /* SQL Analyze(0) */ select /*+  full(t)

  623      29688 oracle@eqd 0000002DDAA90A90 3664547584 9k9zph3d6t3s0      0      2617663258          1       50.30 db file scattered re /* SQL Analyze(0) */ select /*+  full(t)

  702      62908 oracle@eqd 0000002DDAA90A90 3664547584 9k9zph3d6t3s0      0      2617663258          1       50.30 db file scattered re /* SQL Analyze(0) */ select /*+  full(t)

  774      31336 oracle@eqd 0000002DDAA90A90 3664547584 9k9zph3d6t3s0      0      2617663258          1       50.30 db file scattered re /* SQL Analyze(0) */ select /*+  full(t)

  852      17484 oracle@eqd 0000002DDAA90A90 3664547584 9k9zph3d6t3s0      0      2617663258          1       50.30 db file scattered re /* SQL Analyze(0) */ select /*+  full(t)

  931       1917 oracle@eqd 0000002DDAA90A90 3664547584 9k9zph3d6t3s0      0      2617663258          1       50.30 db file scattered re /* SQL Analyze(0) */ select /*+  full(t)

 1005      45861 oracle@eqd 0000002DDAA90A90 3664547584 9k9zph3d6t3s0      0      2617663258          1       50.30 db file scattered re /* SQL Analyze(0) */ select /*+  full(t)

 1081      37925 oracle@eqd 0000002DDAA90A90 3664547584 9k9zph3d6t3s0      0      2617663258          1       50.30 db file scattered re /* SQL Analyze(0) */ select /*+  full(t)

  163      26835 oracle@eqd 0000002DDAA90A90 3664547584 9k9zph3d6t3s0      0      2617663258          1       50.30 ON CPU               /* SQL Analyze(0) */ select /*+  full(t)

On constate bien que le calcul de statistiques se faisait en parallèle puisque j’ai bien 16 sessions correspondant à mes process slaves et une session correspondant à ma session principale. On s’aperçoit également que bien que j’ai plusieurs sessions en parallèles elles travaillent toutes sur un seul objet qui est la table T. Cela rejoint donc ce que je disais en préambule de mon article : le mode parallel ne peut s’effectuer qu’au niveau segment et non pas sur plusieurs tables à la fois.


Test du calcul de stats en mode concurrent :

 

Pour utiliser le mode CONCURRENT il suffit de positionner le paramètre global CONCURRENT qui est désactivé par défaut (même en 12c). Le nombre de sessions concurrentes va dépendre de la valeur du paramètre job_queue_processes.

Voyons ce que le mode CONCURRENT donne pour mon schéma SCHEMA1 :
exec DBMS_STATS.SET_GLOBAL_PREFS('CONCURRENT','TRUE');

exec dbms_stats.gather_schema_stats(ownname => 'SCHEMA1', method_opt => 'for all columns size 1');

ORA-20000: Unable to gather statistics concurrently: Resource Manager is not enabled.

ORA-06512: at "SYS.DBMS_STATS", line 35980

ORA-06512: at line 1
Le message d’erreur m’indique qu’il n’y a pas de Resource Plan de défini, or pour que le calcul de statistiques se fasse en mode concurrent il faut que Ressource Manager puisse gérer les ressources consommées par chaque job. On peut soit définir son propre Resource Plan soit utiliser celui fourni par Oracle qui est le DEFAULT_PLAN :
ALTER SYSTEM SET RESOURCE_MANAGER_PLAN = default_plan;

exec DBMS_STATS.SET_GLOBAL_PREFS('CONCURRENT','TRUE');

exec dbms_stats.gather_schema_stats(ownname => 'SCHEMA1', method_opt => 'for all columns size 1');

PL/SQL procedure successfully completed.
 
Elapsed: 02:25:16.99
Cette fois ci le calcul de stats a pris 2h25 au lieu de 12h55 en mode séquentiel et 11h15 en mode parallèle. Le gain est énorme.

Voici ce qu’on pouvait voir dans V$SESSION pendant que les jobs concurrents tournaient :
SQL> @sql_cur_stmt
 

  SID    SERIAL# PROG       ADDRESS          HASH_VALUE SQL_ID         CHILD PLAN_HASH_VALUE      EXECS   AVG_ETIME EVENT                SQL_TEXT

----- ---------- ---------- ---------------- ---------- ------------- ------ --------------- ---------- ----------- -------------------- -----------------------------------------

  623       9359 oracle@eqd 0000002D32EF62F8 2948485349 3g5vrh6rvwn75      0       968244646          1       12.76 db file scattered re /* SQL Analyze(1) */ select /*+  full(t)

 1085      50128 oracle@eqd 0000002D3518A618 4209280689 4abxz8gxf91pj      1      1515091901          1      113.57 db file sequential r select /*+  no_parallel_index(t, "IHSPOS1

  701       8020 oracle@eqd 0000002CD975A2D8 3982881736 4qj5dp7qqbwy8      1      2053917943          1    1,079.45 db file sequential r select /*+  no_parallel_index(t, "IAVOPEI

   86      30708 oracle@eqd 0000002D148BDE88 1780980331 59p1yadp2g6mb      0               0         36         .02 Streams AQ: waiting  call DBMS_AQADM_SYS.REGISTER_DRIVER (  )

    2      45373 sqlplus.ex 0000002DFC7337D0 3466778119 8kruwyz7a5ph7      0               0          2   23,393.51 Streams AQ: waiting  BEGIN dbms_stats.gather_schema_stats(ownn

  930      47139 oracle@eqd 0000002E0DE75D10 2559624494 96u7bryc91j9f      1       614356722          1    1,905.43 db file sequential r select /*+  no_parallel_index(t, "HISMVC1

  548      12639 oracle@eqd 0000002CD8900280 1387000160 auv55cp9arwb0      1       981273022          1       65.32 db file sequential r select /*+  no_parallel_index(t, "ITPCRO9

  775      22196 oracle@eqd 0000002C7FC02950 2839555010 d92sd5qnn0ay2      1      3067632440          1       42.85 db file sequential r select /*+  no_parallel_index(t, "RTPSPR1

  773      32465 oracle@eqd 0000002CD3983ED0 1227027996 dh8dxq14k5xhw      0      3635621864          1      294.04 db file scattered re /* SQL Analyze(1) */ select /*+  full(t)

  317      19697 oracle@eqd 0000002C7E6227C0 2780047690 dtkjh72kv8aaa      0      3588248285          1         .01 db file scattered re /* SQL Analyze(1) */ select /*+  full(t)

  469      42289 oracle@eqd 0000002CDB8A0F60 1695986908 fzp1yupkjdd6w      1        43487272          1      862.32 db file sequential r select /*+  no_parallel_index(t, "AVICNF8

Comme le paramètre job_queue_processes est positionné à 10 j’ai uniquement 10 sessions concurrentes, et on s’aperçoit que contrairement au calcul de statistiques en parallèle chaque session travaille sur des tables différentes.

On peut penser qu’avec un paramètre job_queue_processes supérieur à 10 il aurait été possible d’obtenir un meilleur résultat.

Il est également possible de combiner le mode concurrent avec le calcul de statistiques en parallèle. Je l’ai testé pour mon schéma mais le gain n’est que de 2 minutes :
-- test calcul de stats concurrent+parallel

exec DBMS_STATS.SET_GLOBAL_PREFS('CONCURRENT','TRUE');

alter system set parallel_adaptive_multi_user=false;

exec dbms_stats.gather_schema_stats(ownname => 'SCHEMA1', degree=> 16, method_opt => 'for all columns size 1');

PL/SQL procedure successfully completed.

Elapsed: 02:23:41.27