12c: Statistiques privées pour les tables temporaires (GTT)

On travaille souvent avec des volumes différents sur les Global Temporary Tables. Assez souvent, le même code est appelé en online pour quelques enregistrements ou en batch pour des millions.
Alors quelles statistiques mettre pour les Global Temporary Table concernées ?

  • Pas de statistiques. les requêtes feront du dynamic sampling.
    Oui mais c’est seulement la première qui va optimiser le plan d’exécution qui va faire ce dynamic sampling. Les suivantes réutiliseront le même plan. Et un jour on va se retrouver avec un batch qui va travailler sur des millions de lignes en faisant un NESTED LOOP qui était prévu pour quelques lignes.
  • L’autre solution jusqu’en 11g est de fixer des stats qui amènent à choisir un plan d’exécution correct pour les 2 cas.
    Très souvent, le plan prévu pour un grand nombre de lignes est valable aussi sur un faible volume. Il vaut faire un HASH JOIN même pour quelques lignes, plutôt que de risquer de faire un NESTED LOOP à partir d’une grosse table.

En 12c, une autre solution: les statistiques privées, propres à la session. Chaque session calcule les stats sur la table temporaire chargée (et en 12c c’est automatique pendant le chargement – cf. post précédent). Et si les stats sont différentes, le curseur ne sera pas partagé. On aura donc toujours un plan d’exécution optimisé pour les données de la session.

Le choix de la visibilité des stats se fait avec dbms_stats.set_table_prefs: GLOBAL_TEMP_TABLE_STATS peut être ‘SHARED’ (comme c’était avant en 11g) ou ‘SESSION’.
La réutilisation d’un curseur dépendra aussi de ces stats.

Comme d’habitude, la demo

12c: Calcul automatique des statistiques au chargement de la table

En 11g après avoir chargé une table il fallait lancer dbms_stats.gather_table_stats pour avoir les statistiques correspondant aux données chargées, ce qui se rajoute au temps de chargement.
Pour les index, les stats sont calculées lors de la création de l’index.

En 12g Oracle calcule les stats pendant qu’il charge, sur un CREATE TABLE AS SELECT, ou un INSERT /*+ APPEND */ (à condition que la table soit vide – suite à un create ou un truncate) – ce qui évite d’aller relire la table ensuite pour un calcul de stats.

Attention, si on veut des histogrammes, ou calculer les stats sur les index existants, ou avoir des stats pour chaque partition, il faudra quand même lancer dbms_stats.gather_table_stats.
Mais avec l’option ‘GATHER AUTO’ dbms_stats ne recalculera que ce qui est nécessaire. Donc on se retrouve quand même avec un gain de temps pour un process ETL par exemple.

Comme d’habitude, la demo complète.

Si l’on veut ne pas calculer les stats – pour garder les anciennes ou pour garder la table sans stats, on peut utiliser le hint NO_GATHER_OPTIMIZER_STATISTICS.
Mais de toute façon, si on veut empêcher un calcul de stats il est mieux d’utiliser dbms_stats.lock_table_stats

Cela s’applique aussi sur les Global Temporary Table, pour les statistiques privées ou partagées.

Calcul de stats sur tables partitionnées

Lorsqu’on charge une partition, il faut calculer les stats dessus.
Mais calculer les stats sur toutes les partitions, et les stats globales sur la table peut être très long.

On peut utiliser options=>’GATHER AUTO’ pour ne calculer les stats que sur ce qui a subi des modifications (la partition chargée et les stats globales)

On peut aussi choisir de ne calculer les stats que sur la partition et laisser les stats globales d’avant (granularity=>’PARTITION’), ou préférer faire une approximation des stats globales (granularity=>’APPROX_GLOBAL AND PARTITION’), puis ne calculer les stats globales que plus tard.

Exemple rapide dans la demo.
(démo en 12c introduisant une nouvelle fonctionnalité – à voir dans un prochain post)

A partir de la 11g, il y a aussi la possibilité d’utiliser les stats incrémentales lorsqu’on a besoin d’histogrammes à jour, où Oracle stocke un Synopsis pour éviter de relire les anciennes partitions.