Oracle n'utilise pas d'index unique pour une clé longue


16

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.):

plans de requête

(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.

Réponses:


7

(Cela répond à l'autre question sur la raison pour laquelle les histogrammes sont différents.)

Les histogrammes sont créés par défaut en fonction de l'inclinaison de la colonne et si la colonne a été utilisée dans un prédicat pertinent. La copie du DDL et des données ne suffit pas, les informations sur la charge de travail sont également importantes.

Selon le Guide de réglage des performances :

Lorsque vous supprimez une table, les informations de charge de travail utilisées par la fonction de collecte d'histogramme automatique et l'historique des statistiques enregistrées utilisées par les procédures RESTORE _ * _ STATS sont perdues. Sans ces données, ces fonctionnalités ne fonctionnent pas correctement.

Par exemple, voici un tableau avec des données asymétriques mais pas d'histogramme:

drop table test1;
create table test1(a date);
insert into test1 select date '2000-01-01'+level from dual connect by level <= 10;
insert into test1 select date '2000-01-01' from dual connect by level <= 1000;
begin
    dbms_stats.gather_table_stats(user, 'TEST1');
end;
/
select histogram from user_tab_columns where table_name = 'TEST1';

HISTOGRAM
---------
NONE

L'exécution de la même chose, mais avec une requête avant la collecte des statistiques, générera un histogramme.

drop table test1;
create table test1(a date);
insert into test1 select date '2000-01-01'+level from dual connect by level <= 10;
insert into test1 select date '2000-01-01' from dual connect by level <= 1000;
select count(*) from test1 where a = sysdate; --Only new line
begin
    dbms_stats.gather_table_stats(user, 'TEST1');
end;
/
select histogram from user_tab_columns where table_name = 'TEST1';

HISTOGRAM
---------
FREQUENCY

2
Exemple brillamment simple. Avez-vous une idée de la raison pour laquelle le CBO utilisait des histogrammes pour les estimations de cardinalité sur un scan unique plutôt que de simplement supposer 1?
Jack Douglas

Merci! J'ai fait une repro complète avec mon type de données et de requêtes sur mon blog: joco.name/2014/01/05/…
fejesjoco

@ Jack, je pense que c'est la paresse. Les ingénieurs Oracle doivent avoir pensé que les statistiques d'un index unique auront le même nombre de valeurs distinctes que les lignes, donc l'hypothèse de 1 cardinalité n'est pas câblée, mais simplement utilisée à partir des statistiques, comme dans tout autre cas. De plus, dans un cas général, les histogrammes l'emportent sur les statistiques simples. Mon cas semble être très spécial à cause des touches longues uniquement, mais je pense que cela fonctionne plutôt bien sinon.
fejesjoco

@fejesjoco Je pense que l'explication de JL est plus probable, car les histogrammes auraient également éclipsé les statistiques générales dans le cas d'une seule recherche (sans le 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 gros UNION ALLmais il peut y avoir d'autres raisons de ne pas le faire et JL mentionne d'autres solutions possibles dans le blog lié.
Jack Douglas

1
Un autre mystère mineur à considérer - comment cet histogramme a-t-il été créé en premier lieu? Oracle ne semble considérer une colonne comme asymétrique que si elle comporte des doublons, ce que votre colonne unique ne peut évidemment pas avoir. Est-ce que quelqu'un a construit intentionnellement cet histogramme (peu probable), ou quelqu'un a-t-il rassemblé des statistiques avec le non recommandé method_opt=>'for all indexed columns'?
Jon Heller

8

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


2
Je l'ai mentionné dans notre salle de chat :) chat.stackexchange.com/transcript/message/12987649#12987649
Philᵀᴹ

Je n'ai pas vu ça :). Donc, la seule chose étrange est pourquoi il y avait des histogrammes dans la première table et non dans le clone, je pensais que recueillir_schema_stats avait tout mis à jour, apparemment non.
fejesjoco

6

J'ai envoyé un courriel à Jonathan Lewis à ce sujet et j'ai obtenu une réponse très utile:

La bizarrerie dans le calcul est une conséquence des limites des histogrammes basés sur les caractères, voir en particulier:

http://jonathanlewis.wordpress.com/2010/10/13/frequency-histogram-5/ http://jonathanlewis.wordpress.com/2010/10/19/frequency-histograms-6/

En regardant l'exemple, la requête est pour une liste IN, pas pour une seule ligne, donc ma supposition initiale serait que l'optimiseur a utilisé une stratégie générique pour calculer la sélectivité sur plusieurs lignes plutôt que d'avoir un morceau de code de cas spécial pour un Liste IN sur une clé primaire. Je suppose qu'il ne serait pas trop difficile pour eux de reconnaître ce cas, mais les développeurs n'ont probablement pas considéré que cela en valait la peine.

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:

Conclusion : si vous avez des chaînes assez longues et similaires dans une colonne qui est un bon candidat pour un histogramme de fréquence (par exemple une colonne d'état très descriptive), vous avez un problème si une valeur très rare semble identique à une très populaire valeur jusqu'aux 32 premiers caractères. Vous pouvez constater que la seule solution consiste à modifier la liste des valeurs légales (bien que diverses stratégies impliquant des colonnes virtuelles ou des index basés sur des fonctions puissent contourner le problème).


Malheureusement, les histogrammes semblent être une fonctionnalité peu connue, je suppose que c'est parce que c'est trop profond pour un développeur SQL et la plupart du temps ils fonctionnent, mais il est bon de savoir qu'il existe de nombreuses ressources à ce sujet, je ne regardais tout simplement pas dans le bons endroits :). Il est assez mauvais qu'Oracle coupe à 32 octets et prenne des décisions désastreuses sur cette base. Heureusement, je n'ai besoin d'aucune modification, la suppression des histogrammes est une solution parfaite. Les valeurs clés sont uniques, je recherche toujours 20 valeurs à la fois, cela fonctionne très bien avec un index uniquement, et c'est déterministe. Mais je n'utiliserai pas de touches longues la prochaine fois, c'est sûr.
fejesjoco

Les histogrammes sont assez bien connus des administrateurs de bases de données;) J'aime le fait que vous semblez vouloir apprendre des choses plus approfondies et que vous pensez vraiment que vous devriez lire le livre de JL, c'est très très bon. Le CBO fait généralement un excellent travail: il y aura toujours des cas marginaux qui doivent être examinés, mais il convient de garder à l'esprit que même sans coupure, les estimations ne sont toujours que des estimations.
Jack Douglas

1
Si vous exécutez un travail de statistiques régulier (comme celui qu'Oracle exécute par défaut sur une installation propre), vous pouvez trouver des histogrammes réapparaître, vous devrez peut-être chercher un moyen d'empêcher cela (comme LOCK_TABLE_STATS peut-être)
Jack Douglas

J'ai mentionné un article de blog dans ma réponse, il y a des instructions sur la façon d'empêcher les histogrammes d'une colonne.
fejesjoco

1
@Jack Douglas, merci d'avoir impliqué J. Lewis et de nous avoir rendu compte!
Dimitre Radoulov
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.