Ich habe ein Client-Programm, das eine Abfrage für eine Ansicht ausführt, die eine Tabelle mit einer anderen verknüpft. Die Leistung ist schlecht und ich habe versucht, sie durch Hinzufügen des richtigen Index zu optimieren. Die fragliche Abfrage verwendet tatsächlich nur die zweite Tabelle, daher habe ich diese Tabelle direkt getestet.
Ich habe (mehrere) Indizes gefunden, die für die Abfrage für die Tabelle gut funktionierten, aber als ich sie auf die Ansicht umstellte, verwendeten sie keine Indizes mehr und führten stattdessen nur vollständige Scans für beide Tabellen durch. Da diese Tabellen groß sind (jeweils 2-3 Millionen Zeilen), ist dies sehr langsam.
Zum einfachen Testen habe ich die Abfrage geändert, um die zu umgehen und nur den äußeren Join in die Abfrage selbst zu integrieren. Dies reproduzierte das Problem erfolgreich, ließ jedoch das Rätsel offen, warum der äußere Join die Indizes nicht verwenden würde.
Hier ist die Tabelle mit allen Indizes, die ich beim Testen hinzugefügt habe:
CREATE TABLE TEST_DATA
(ID NUMBER(11,0) PRIMARY KEY,
FORMATTED_RESULT VARCHAR2(255 BYTE),
F_RESULT NUMBER,
IDNUM NUMBER(11,0),
IDNUM_DESCRIPTION VARCHAR2(128 BYTE),
LAB_NUMBER NUMBER(11,0),
SEQ_NUMBER NUMBER(11,0),
ORDERNO NUMBER(11,0),
SUPPL_FORMATTED_RESULT VARCHAR2(255 BYTE),
SUPPL_IDNUM NUMBER(11,0),
SUPPL_IDNUM_DESCRIPTION VARCHAR2(128 BYTE),
SUPPL_UNIT VARCHAR2(16 BYTE)
) ;
CREATE UNIQUE INDEX TEST_LN_SQN_ORDER ON TEST_DATA (LAB_NUMBER, SEQ_NUMBER, ORDERNO) ;
CREATE INDEX TEST_LN_SQN ON TEST_DATA (LAB_NUMBER, SEQ_NUMBER) ;
CREATE INDEX TD_CUIDD_CUFR ON TEST_DATA (UPPER(COALESCE(SUPPL_IDNUM_DESCRIPTION,IDNUM_DESCRIPTION)), UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT))) ;
CREATE INDEX TD_UFR_IDN ON TEST_DATA (UPPER(FORMATTED_RESULT), IDNUM) ;
CREATE INDEX TD_UIDD_UFR ON TEST_DATA (UPPER(IDNUM_DESCRIPTION), UPPER(FORMATTED_RESULT)) ;
CREATE INDEX TD_CUFR_CIDN_SN_LN ON TEST_DATA (UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT)), COALESCE(SUPPL_IDNUM,IDNUM), SEQ_NUMBER, LAB_NUMBER) ;
CREATE INDEX TD_SN_LN_CUFR_CIDN ON TEST_DATA (SEQ_NUMBER, LAB_NUMBER, UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT)), COALESCE(SUPPL_IDNUM,IDNUM)) ;
CREATE INDEX TD_CUFR_CIDN ON TEST_DATA (UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT)), COALESCE(SUPPL_IDNUM,IDNUM)) ;
Hier ist die andere Tabelle (die wir für diese Abfrage nicht wirklich verwenden)
CREATE TABLE REQUEST_INFO
(NUMBER(11,0) PRIMARY KEY,
CHARGE_CODE VARCHAR2(32 BYTE),
LAB_NUMBER NUMBER(11,0),
SEQ_NUMBER NUMBER(11,0)
) ;
CREATE INDEX RI_LN_SN ON REQUEST_INFO (LAB_NUMBER, SEQ_NUMBER) ;
CREATE INDEX RI_SN_LN ON REQUEST_INFO (SEQ_NUMBER, LAB_NUMBER) ;
Hier ist also zuerst die Abfrage für die einzelne Tabelle direkt, die einen der Indizes erfolgreich verwendet.
-- GOOD, Uses index : TD_CUFR_CIDN_SN_LN
select td.LAB_NUMBER
from test_DATA td
where UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT))='491(10)376'
and COALESCE(TD.SUPPL_IDNUM, TD.IDNUM)=40549
;
Hier ist die Abfrage, bei der beide Tabellen mit einem inneren Join verwendet werden. Dies verwendet auch die Indizes und läuft schnell.
-- GOOD, Uses indexes : TD_CUFR_CIDN_SN_LN AND RI_SN_LN
select TD.LAB_NUMBER
from REQUEST_INFO RI
JOIN TEST_DATA TD ON TD.LAB_NUMBER = RI.LAB_NUMBER AND TD.SEQ_NUMBER = RI.SEQ_NUMBER
where UPPER(COALESCE(TD.SUPPL_FORMATTED_RESULT,TD.FORMATTED_RESULT))='491(10)376'
and COALESCE(TD.SUPPL_IDNUM, TD.IDNUM)=40549
Und hier ist dieselbe Abfrage mit einem Left Outer Join, wie sie in der Ansicht geschrieben ist. Dies verwendet KEINEN der Indizes und läuft sehr langsam.
-- BAD, does not use indexes
select TD.LAB_NUMBER
from REQUEST_INFO RI
LEFT JOIN TEST_DATA TD ON TD.LAB_NUMBER = RI.LAB_NUMBER AND TD.SEQ_NUMBER = RI.SEQ_NUMBER
where UPPER(COALESCE(TD.SUPPL_FORMATTED_RESULT,TD.FORMATTED_RESULT))='491(10)376'
and COALESCE(TD.SUPPL_IDNUM, TD.IDNUM)=40549
;
Bevor es jemand sagt: Diese Abfrage ist tatsächlich logisch identisch mit der vorherigen. Dies liegt daran, dass die WHERE-Klausel nach Spalten aus der äußeren Tabelle (TD) filtert, wodurch eine äußere Verknüpfung effektiv / logisch in eine innere Verknüpfung umgewandelt wird (aus diesem Grund ist es wichtig, ob Bedingungen in der ON-Klausel gegenüber der WHERE-Klausel auftreten).
Um die Verrücktheit noch zu verstärken, entschied ich mich zu sehen, was passieren würde, wenn ich den Zwang von außen nach innen expliziter machen würde:
-- GOOD, Uses indexes : TD_CUFR_CIDN_SN_LN AND RI_SN_LN
select TD.LAB_NUMBER
from REQUEST_INFO RI
LEFT JOIN TEST_DATA TD ON TD.LAB_NUMBER = RI.LAB_NUMBER AND TD.SEQ_NUMBER = RI.SEQ_NUMBER
where UPPER(COALESCE(TD.SUPPL_FORMATTED_RESULT,TD.FORMATTED_RESULT))='491(10)376'
and COALESCE(TD.SUPPL_IDNUM, TD.IDNUM)=40549
and TD.LAB_NUMBER IS NOT NULL
;
Unglaublich, das hat funktioniert!
Die Frage hier ist also: 1) WARUM findet Oracle das nicht selbst heraus?
Und 2) Gibt es eine Einstellung oder einen Index usw., die ich erstellen kann, damit Oracle dies richtig herausfindet und die Indizes verwendet?
Weitere Überlegungen:
Die Ansicht wird von einer Vielzahl anderer Abfragen und Clients verwendet, daher kann ich sie für diese eine Abfrage nicht einfach in einen inneren Join ändern.
Der Client generiert die Abfrage, daher ist es schwierig / nahezu unmöglich, die Abfrage unter skurrilen Sonderfallbedingungen wie: " Verwenden Sie diese Ansicht für diese Daten, es sei denn, Sie benötigen nur diese Spalten aus dieser einen Tabelle, und verwenden Sie dann eine andere view "oder" Wenn Sie diese Spalten und nur diese Spalten aus dieser einen Tabelle benötigen, fügen Sie der WHERE-Klausel ein 'IS NOT NULL' hinzu. "
Anregungen oder Erkenntnisse sind willkommen.
UPDATE: Ich habe es gerade auch unter Oracle 11g versucht. Dort erhalte ich genau die gleichen Ergebnisse.
Auf Anfrage ist hier die Ausgabe des Erklärungsplans, zuerst die gute Version, in der Indizes verwendet werden:
Rows Plan COST Predicates
3 SELECT STATEMENT 8
3 HASH JOIN 8 Access:TD.LAB_NUMBER=RI.LAB_NUMBER AND TD.SEQ_NUMBER=RI.SEQ_NUMBER
3 NESTED LOOPS 8
STATISTICS COLLECTOR
3 INDEX RANGE SCAN TD_CUFR_CIDN_SN_LN 4 Access:UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT))='491(10)376' AND COALESCE(SUPPL_IDNUM,IDNUM)=40549, Filter:TD.LAB_NUMBER IS NOT NULL
1 INDEX RANGE SCAN RI_SN_LN 2 Access:TD.SEQ_NUMBER=RI.SEQ_NUMBER AND TD.LAB_NUMBER=RI.LAB_NUMBER
1 INDEX FAST FULL SCAN RI_SN_LN 2
Und jetzt die schlechte Version:
Rows Plan COST Predicates
31939030 SELECT STATEMENT 910972
FILTER Filter:UPPER(COALESCE(SUPPL_FORMATTED_RESULT,FORMATTED_RESULT))='491(10)376' AND COALESCE(SUPPL_IDNUM,IDNUM)=40549
31939030 HASH JOIN OUTER 910972 Access:TD.LAB_NUMBER(+)=RI.LAB_NUMBER AND TD.SEQ_NUMBER(+)=RI.SEQ_NUMBER
6213479 TABLE ACCESS FULL REQUEST_INFO 58276
56276228 TABLE ACCESS FULL TEST_DATA 409612
quelle
Antworten:
Dies ist meistens eine teilweise Antwort auf Teil 1 mit einigen Spekulationen. Sie und ich wissen, dass die folgende Abfrage:
Entspricht dieser Abfrage:
Dies bedeutet jedoch nicht, dass Oracle weiß, dass die beiden Abfragen gleichwertig sind. Die Äquivalenz der beiden Abfragen ist erforderlich, damit Oracle den
TD_CUFR_CIDN_SN_LN
Index verwenden kann. Was wir hoffen , denn hier ist eineOUTER JOIN
zuINNER JOIN
Konvertierung. Ich hatte nicht viel Glück , gute Informationen darüber zu finden. Schauen wir uns also die Erklärungspläne an:Das Hinzufügen
TD.LAB_NUMBER IS NOT NULL
zurWHERE
Klausel ist eine sehr direkte Möglichkeit, Oracle darüber zu informieren, dassOUTER JOIN
eineINNER JOIN
Konvertierung möglich ist. Wir können sehen, dass es aufgetreten ist, indem wir die hervorgehobene Linie betrachten. Ich denke, dass so ziemlich jede Spalte die Konvertierung zulässt, obwohl die Auswahl der falschen Spalte die Abfrageergebnisse ändern kann.Wenn wir einen etwas komplizierteren Filter wie diesen versuchen, findet
(TD.LAB_NUMBER IS NOT NULL OR TD.SEQ_NUMBER IS NOT NULL)
die Join-Konvertierung nicht statt:Wir können darauf hinweisen, dass dies
OUTER JOIN
wirklich ein ist,INNER JOIN
aber der Abfrageoptimierer wurde möglicherweise nicht dafür programmiert. In der ursprünglichen Abfrage haben Sie einenCOALESCE()
Ausdruck, der wahrscheinlich zu komplex ist, als dass der Abfrageoptimierer die Abfragetransformation anwenden könnte.Hier ist eine DB-Geige für einige Beispiele.
Bei der zweiten Frage kann ich mir keine Möglichkeit vorstellen, dies zu umgehen. Sie könnten versuchen, die Tabelleneliminierung zu nutzen . Wie Sie sagten, benötigt diese Abfrage nicht einmal die
REQUEST_INFO
Tabelle. Es gibt jedoch einige Einschränkungen:Vielleicht gibt es eine Möglichkeit, dies für dieses Problem zu verwenden, aber ich kann die Einschränkungen nicht umgehen.
quelle
Ersetzen Sie die Coalesce-Anweisung durch eine
OR
Anweisung, da beim Hinzufügen einer Funktion auf der linken Seite der Where-Klausel kein Index verwendet wird, es sei denn, die linksseitige Funktion wird mithilfe eines funktionsbasierten Index indiziert. Ändern Sie daher die Abfrage wie folgt. Der separate Index fürSUPPL_FORMATTED_RESULT
undFORMATTED_RESULT
sollte eineupper
Funktion für die folgende Abfrage haben , um den Indexzugriff zu verwenden.Hinweis: Wenn die Daten einen Versatz aufweisen und die Anzahl der Datensätze mit den Werten '491 (10) 376' und 40549 größer ist, wird der Index übersprungen und ein vollständiger Tabellenscan verwendet.
quelle