J'ai une table avec 250K lignes dans ma base de données de test. (Il y a quelques centaines de millions en production, nous pouvons y observer le même problème.) La table a un identifiant de chaîne nvarchar2 (50), non nul, avec un index unique (ce n'est pas le PK).
Les identifiants sont constitués d'une première partie qui a 8 valeurs différentes dans ma base de données de test (et environ un millier en production), puis un signe @ et enfin un nombre de 1 à 6 chiffres. Par exemple, il peut y avoir 50 000 lignes commençant par «ABCD_BGX1741F_2006_13_20110808.xml @», suivies de 50 000 nombres différents.
Lorsque je recherche une seule ligne en fonction de son identifiant, la cardinalité est estimée à 1, le coût est très faible, cela fonctionne très bien. Lorsque je recherche plusieurs lignes avec plusieurs identifiants dans une expression IN ou une expression OR, les estimations de l'index sont complètement fausses, donc une analyse complète de la table est utilisée. Si je force l'index avec un indice, c'est très rapide, le scan complet de la table est en fait exécuté un ordre de grandeur plus lent (et beaucoup plus lent en production). C'est donc un problème d'optimiseur.
Comme test, j'ai dupliqué la table (dans le même schéma + espace de table) avec exactement le même DDL et exactement le même contenu. J'ai recréé l'index unique sur la première table pour faire bonne mesure et créé exactement le même index sur la table de clonage. J'ai fait un DBMS_STATS.GATHER_SCHEMA_STATS('schemaname',estimate_percent=>100,cascade=>true);
. Vous pouvez même voir que les noms d'index sont consécutifs. Alors maintenant, la seule différence entre les deux tables est que la première a été chargée dans un ordre aléatoire sur une longue période, avec des blocs dispersés sur le disque (dans un espace de table avec plusieurs autres grandes tables), la seconde a été chargée en une seule fois INSERT-SELECT. A part ça, je ne peux imaginer aucune différence. (Le tableau d'origine a été réduit depuis la dernière grande suppression, et il n'y a pas eu une seule suppression après cela.)
Voici les plans de requête pour les malades et la table des clones (les chaînes sous le pinceau noir sont les mêmes sur toute l'image, et également sous leur pinceau gris.):
(Dans cet exemple, 1867 lignes commencent par l'identifiant noir. Une requête à 2 lignes produit une cardinalité de 1867 * 2, une requête à 3 lignes produit une cardinalité de 1867 * 3, etc. être une coïncidence, Oracle semble ne pas se soucier de la fin des identifiants.)
Qu'est-ce qui pourrait provoquer ce comportement? Évidemment, il serait assez coûteux de recréer la table en production.
USER_TABLES: http://i.stack.imgur.com/nDWze.jpg USER_INDEXES: http://i.stack.imgur.com/DG9um.jpg J'ai uniquement changé le schéma et le nom de l'espace disque logique . Vous pouvez voir que les noms de table et d'index sont les mêmes que sur la capture d'écran du plan de requête.
la source
in
), n'est-ce pas? Je pense que le CBO fait l'hypothèse de cardinalité 1, mais seulement dans le cas le plus simple. Je suppose que vous pouvez contourner le tout en utilisant un grosUNION ALL
mais il peut y avoir d'autres raisons de ne pas le faire et JL mentionne d'autres solutions possibles dans le blog lié.method_opt=>'for all indexed columns'
?J'ai trouvé la solution! C'est tellement beau et j'ai en fait beaucoup appris sur Oracle.
En un mot: histogrammes.
J'ai commencé à lire beaucoup sur le fonctionnement du CBO d'Oracle et je suis tombé sur des histogrammes. Je ne comprenais pas complètement alors j'ai jeté un œil à la table USER_HISTOGRAMS et voilá. Il y avait plusieurs rangées pour la table des malades et pratiquement rien pour la table clonée. Pour la table malade, il y avait une ligne pour chacune des 8 différentes parties de départ identifiant. Et c'est la clé: ils ont été coupés à 32 caractères, avant le signe @. Comme je l'ai dit, la première partie des clés est très répétitive, elles deviennent différentes après le signe @.
Il semble que les histogrammes puissent être plus puissants que le simple fait qu'un index unique a toujours une cardinalité de 0 ou 1 pour une valeur donnée. Lorsque je cherchais plus de 2 lignes, Oracle a regardé l'histogramme, il a pensé qu'il pourrait y avoir des dizaines de milliers de valeurs pour cette partie de départ identifiant, et il a fait dévier le CBO.
J'ai supprimé les histogrammes de cette colonne dans l'ancien tableau et le problème a disparu!
Plus de lecture: https://blogs.oracle.com/optimizer/entry/how_do_i_drop_an_existing_histogram_on_a_column_and_stop_the_auto_stats_gathering_job_from_creating
la source
J'ai envoyé un courriel à Jonathan Lewis à ce sujet et j'ai obtenu une réponse très utile:
J'ai fortement recommandé de lire les articles de blog qu'il relie, ils décrivent en détail la limitation des histogrammes que vous utilisez, par exemple:
la source