Oracle verwendet keinen eindeutigen Index für einen langen Schlüssel

16

Ich habe eine Tabelle mit 250 KB Zeilen in meiner Testdatenbank. (Es werden ein paar Hundert Millionen produziert, wir können dort dasselbe Problem beobachten.) Die Tabelle hat eine Zeichenfolgen-ID nvarchar2 (50), die nicht null ist und einen eindeutigen Index enthält (es ist nicht die PK).

Die Bezeichner bestehen aus einem ersten Teil mit 8 verschiedenen Werten in meiner Testdatenbank (und ungefähr tausend in der Produktion), einem @ -Zeichen und schließlich einer 1- bis 6-stelligen Zahl. Zum Beispiel könnte es 50.000 Zeilen geben, die mit 'ABCD_BGX1741F_2006_13_20110808.xml @' beginnen, gefolgt von 50.000 verschiedenen Zahlen.

Wenn ich eine einzelne Zeile basierend auf ihrem Bezeichner abfrage, wird die Kardinalität auf 1 geschätzt, die Kosten sind sehr gering und es funktioniert einwandfrei. Wenn ich mehr als eine Zeile mit mehreren Bezeichnern in einem IN-Ausdruck oder einem OR-Ausdruck abfrage, sind die Schätzungen für den Index völlig falsch, sodass ein vollständiger Tabellenscan verwendet wird. Wenn ich den Index mit einem Hinweis erzwinge, ist er sehr schnell, der vollständige Tabellenscan wird tatsächlich eine Größenordnung langsamer ausgeführt (und viel langsamer in der Produktion). Es ist also ein Optimierungsproblem.

Als Test habe ich die Tabelle (im selben Schema + Tablespace) mit genau derselben DDL und genau demselben Inhalt dupliziert. Ich habe den eindeutigen Index für die erste Tabelle aus gutem Grund neu erstellt und genau denselben Index für die Klontabelle erstellt. Ich habe eine DBMS_STATS.GATHER_SCHEMA_STATS('schemaname',estimate_percent=>100,cascade=>true);. Sie können sogar sehen, dass die Indexnamen fortlaufend sind. Der einzige Unterschied zwischen den beiden Tabellen besteht nun darin, dass die erste über einen langen Zeitraum in zufälliger Reihenfolge geladen wurde, wobei Blöcke auf der Festplatte verteilt waren (in einem Tablespace zusammen mit mehreren anderen großen Tabellen). Die zweite Tabelle wurde als Stapel geladen INSERT-SELECT. Ansonsten kann ich mir keinen Unterschied vorstellen. (Die Originaltabelle wurde seit der letzten großen Löschung verkleinert, und danach gab es keine einzige Löschung mehr.)

Hier sind die Abfragepläne für die Kranken- und die Klontabelle (Die Zeichenfolgen unter dem schwarzen Pinsel sind auf dem gesamten Bild und auch unter dem grauen Pinsel gleich.):

Abfragepläne

(In diesem Beispiel gibt es 1867-Zeilen, die mit dem schwarz gepinselten Bezeichner beginnen. Eine 2-zeilige Abfrage ergibt eine Kardinalität von 1867 * 2, eine 3-zeilige Abfrage ergibt eine Kardinalität von 1867 * 3 usw.) Zufall, Oracle scheint sich nicht um das Ende der Bezeichner zu kümmern.)

Was könnte dieses Verhalten verursachen? Offensichtlich wäre es ziemlich teuer, die Tabelle in der Produktion neu zu erstellen.

USER_TABLES: http://i.stack.imgur.com/nDWze.jpg USER_INDEXES: http://i.stack.imgur.com/DG9um.jpg Ich habe nur den Namen des Schemas und des Tabellenbereichs geändert. Sie können sehen, dass die Tabellen- und Indexnamen mit denen auf dem Bildschirmfoto des Abfrageplans übereinstimmen.

fejesjoco
quelle

Antworten:

7

(Dies beantwortet die andere Frage, warum die Histogramme unterschiedlich sind.)

Histogramme werden standardmäßig basierend auf dem Spaltenversatz und der Frage erstellt, ob die Spalte in einem relevanten Prädikat verwendet wurde. Das Kopieren der DDL und der Daten ist nicht ausreichend. Die Informationen zur Arbeitslast sind ebenfalls wichtig.

Laut dem Performance Tuning Guide :

Wenn Sie eine Tabelle löschen, gehen die von der Funktion zum automatischen Erfassen von Histogrammen verwendeten Workload-Informationen und der gespeicherte Statistikverlauf, der von den Prozeduren RESTORE _ * _ STATS verwendet wird, verloren. Ohne diese Daten funktionieren diese Funktionen nicht ordnungsgemäß.

Zum Beispiel ist hier eine Tabelle mit verzerrten Daten, aber ohne Histogramm:

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

Wenn Sie dasselbe ausführen, aber eine Abfrage durchführen, bevor die Statistiken erfasst wurden, wird ein Histogramm erstellt.

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
Jon Heller
quelle
2
Genial einfaches Beispiel. Haben Sie eine Idee, warum der CBO Histogramme für Kardinalitätsschätzungen bei einem eindeutigen Scan verwendete, anstatt nur 1 anzunehmen?
Jack Douglas
Vielen Dank! Ich habe einen vollständigen Repro mit meinen Daten und Abfragen in meinem Blog gemacht: joco.name/2014/01/05/…
fejesjoco
@ Jack Ich denke, es ist Faulheit. Die Oracle-Ingenieure müssen davon ausgegangen sein, dass die Statistiken eines eindeutigen Index die gleiche Anzahl unterschiedlicher Werte wie Zeilen aufweisen, sodass die 1-Kardinalitätsannahme nicht fest verdrahtet ist, sondern wie in jedem anderen Fall einfach anhand der Statistiken verwendet wird. Im Allgemeinen trumpfen Histogramme auch vor einfachen Statistiken. Mein Fall scheint nur wegen der langen Tasten etwas Besonderes zu sein, aber ich glaube, dass dies ansonsten ziemlich gut funktioniert.
Fejesjoco
@fejesjoco Ich denke, die Erklärung von JL ist wahrscheinlicher, da die Histogramme bei einem einzelnen Lookup (ohne das in) auch die allgemeinen Statistiken übertroffen hätten, nicht wahr ? Ich denke, der CBO nimmt die Kardinalität 1 an, aber nur im einfachsten Fall. Ich nehme an, Sie könnten das Ganze mit einem großen umgehen, UNION ALLaber es kann andere Gründe geben, dies nicht zu tun, und JL erwähnt andere mögliche Problemumgehungen in dem verlinkten Blog-Beitrag.
Jack Douglas
1
Ein weiteres kleines Rätsel: Wie ist dieses Histogramm überhaupt entstanden? Oracle scheint eine Spalte nur dann als verzerrt zu betrachten, wenn sie Duplikate enthält, die Ihre eindeutige Spalte offensichtlich nicht haben kann. Hat jemand dieses Histogramm absichtlich erstellt (unwahrscheinlich) oder hat jemand Statistiken mit den nicht empfohlenen Daten gesammelt method_opt=>'for all indexed columns'?
Jon Heller
8

Ich habe die Lösung gefunden! Es ist so schön und ich habe tatsächlich eine Menge über Oracle gelernt.

Mit einem Wort: Histogramme.

Ich las viel darüber, wie Oracle CBO funktioniert, und stieß auf Histogramme. Ich habe es nicht ganz verstanden, also habe ich mir die Tabelle USER_HISTOGRAMS und voilá angesehen. Es gab mehrere Zeilen für den Kranken-Tisch und praktisch nichts für den geklonten Tisch. Für die Krankentabelle gab es eine Zeile für jedes der 8 verschiedenen Identifikator-Startteile. Und das ist der Schlüssel: Sie wurden mit 32 Zeichen vor dem @ -Zeichen abgeschnitten. Wie gesagt, der erste Teil der Tasten wiederholt sich stark, sie werden nach dem @ -Zeichen anders.

Es scheint, dass Histogramme leistungsfähiger sein können als die einfache Tatsache, dass ein eindeutiger Index für einen bestimmten Wert immer eine Kardinalität von 0 oder 1 hat. Als ich nach mehr als 2 Zeilen fragte, sah Oracle sich das Histogramm an, dachte, dass es Zehntausende von Werten für diesen Identifier-Startteil geben könnte, und warf den CBO vom Kurs ab.

Ich habe die Histogramme für diese Spalte in der alten Tabelle gelöscht und das Problem ist behoben!

Weitere Informationen: 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

fejesjoco
quelle
2
Ich habe das in unserem Chatroom erwähnt :) chat.stackexchange.com/transcript/message/12987649#12987649
Philᵀᴹ
Das habe ich nicht gesehen :). Das einzig seltsame ist, warum es Histogramme in der ersten Tabelle und nicht im Klon gab. Ich dachte, gather_schema_stats hat alles aktualisiert, anscheinend nicht.
Fejesjoco
6

Ich habe Jonathan Lewis eine E-Mail darüber geschickt und eine sehr hilfreiche Antwort erhalten:

Die Seltsamkeit bei der Berechnung ist eine Folge der Grenzwerte für zeichenbasierte Histogramme, siehe insbesondere:

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

Betrachtet man das Beispiel, so bezieht sich die Abfrage auf eine IN-Liste und nicht auf eine einzelne Zeile. Meine anfängliche Vermutung lautet daher, dass das Optimierungsprogramm eine generische Strategie zur Berechnung der Mehrzeilenselektivität verwendet hat, anstatt einen Sonderfallcode für eine zu verwenden IN-Liste auf einem Primärschlüssel. Ich denke, es wäre nicht zu schwer für sie, diesen Fall zu erkennen, aber die Entwickler haben es wahrscheinlich nicht für die Mühe wert gehalten.

Ich empfehle dringend, die von ihm gelinkten Blog-Beiträge zu lesen. Sie beschreiben detailliert die Begrenzung der Histogramme, in denen Sie sich befinden, z. B .:

Fazit : Wenn Sie ziemlich lange und ähnliche Zeichenfolgen in einer Spalte haben, die ein guter Kandidat für ein Frequenzhistogramm ist (z. B. eine sehr beschreibende Statusspalte), haben Sie ein Problem, wenn ein sehr seltener Wert mit einem sehr beliebten identisch aussieht Wert bis zu den ersten 32 Zeichen. Möglicherweise besteht die einzige Lösung darin, die Liste der zulässigen Werte zu ändern (obwohl verschiedene Strategien mit virtuellen Spalten oder funktionsbasierten Indizes das Problem umgehen können).

Jack Douglas
quelle
Leider scheinen Histogramme ein wenig bekanntes Feature zu sein. Ich vermute, es ist zu tief für einen SQL-Entwickler und die meiste Zeit funktionieren sie nur, aber es ist gut zu wissen, dass es viele Ressourcen gibt, ich habe nur nicht in der nachgesehen richtige Orte :). Es ist ziemlich schlimm, dass Oracle bei 32 Bytes schneidet und auf dieser Grundlage katastrophale Entscheidungen trifft. Zum Glück brauche ich keine Anpassungen, das Löschen der Histogramme ist eine perfekte Lösung. Die Schlüsselwerte sind eindeutig, ich suche immer nach jeweils 20 Werten, es funktioniert nur mit einem Index und es ist deterministisch. Aber ich werde das nächste Mal keine langen Schlüssel verwenden, das ist sicher.
Fejesjoco
Histogramme sind unter Datenbankadministratoren ziemlich bekannt;) Ich finde es toll, dass Sie gerne etwas tieferes lernen möchten und wirklich denken, Sie sollten das Buch von JL lesen , es ist sehr, sehr gut. Das CBO leistet im Allgemeinen gute Arbeit: Es wird immer Randfälle geben, die untersucht werden müssen, aber es ist zu berücksichtigen, dass Schätzungen auch ohne den Cutoff immer nur Schätzungen sind.
Jack Douglas
1
Wenn Sie einen regulären Statistikjob ausführen (wie den, den Oracle standardmäßig bei einer Neuinstallation ausführt ), werden möglicherweise wieder Histogramme angezeigt. Möglicherweise müssen Sie nach Möglichkeiten suchen, dies zu verhindern (z. B. LOCK_TABLE_STATS, möglicherweise)
Jack Douglas
In meiner Antwort habe ich einen Blog-Beitrag erwähnt. Es gibt Anweisungen, wie Sie Histogramme für eine Spalte verhindern können.
Fejesjoco
1
@Jack Douglas, danke, dass du J. Lewis involviert hast und darüber berichtet hast!
Dimitre Radoulov