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