CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] Inhaltsverzeichnis 1. 2. Eine Trace Datei _______________________________________________________ 6 1.1. Session Trace __________________________________________________________ 7 1.2. Trace einer beliebigen Session ____________________________________________ 7 1.3. Probleme beim Erstellen einer Trace Datei__________________________________ 8 1.4. Autotrace______________________________________________________________ 9 Trace Datei und TKProf Ausgabe ________________________________________ 11 2.1. Das Trace File im Überblick _____________________________________________ 11 2.1.1. Beschreibung der Ergebnisse in einer Trace Datei __________________________________ 15 2.2. Erstellen einer TKProf Datei ____________________________________________ 17 2.3. Interpretieren einer TKProf Datei ________________________________________ 19 2.3.1. 2.3.2. 3. Darstellung der Ergebnisse in einer TKProf Datei __________________________________ 20 Was bildet die Differenz zwischen CPU- und elapsed time____________________________ 30 So arbeitet Oracle _____________________________________________________ 33 3.1. Auflösung eines Statement ______________________________________________ 33 3.2. Der Index_____________________________________________________________ 41 3.2.1. 3.2.2. 3.3. Ungünstiger CLUSTERING_INDEX ____________________________________________ 41 Index Selektivität ____________________________________________________________ 43 Verschiedene Indextypen________________________________________________ 48 3.3.1. 3.3.2. 3.3.3. 3.4. B*tree Index _______________________________________________________________ 48 Bitmap Index _______________________________________________________________ 53 Hash Index_________________________________________________________________ 53 Wann Oracle einen Index im CHOOSE Mode nicht nutzt ____________________ 53 3.4.1. 3.4.2. 3.4.3. 3.4.4. 3.4.5. 3.4.6. 3.4.7. 3.4.8. 3.4.9. 3.4.10. 3.4.11. 3.4.12. 3.4.13. 3.4.14. 4. Zu große Anzahl von gelöscht markierten Indexeinträgen ____________________________ Geringes Datenvolumen der Tabelle _____________________________________________ Einschränkung mit != oder <> __________________________________________________ Unbekannte Selektivität durch Bereichsabfragen ___________________________________ Unbekannte Selektivität durch Einsatz von Bindevariablen ___________________________ Unterbrechung der Reihenfolge von Indexattributen_________________________________ Füllgrad des Index-Key _______________________________________________________ Fehlende Statistiken__________________________________________________________ Veraltete Statistiken__________________________________________________________ Abfrage mit IS NULL ______________________________________________________ Abfrage IS NOT NULL ____________________________________________________ Mehrere Indizes __________________________________________________________ Ungünstiger Clustering_Index _______________________________________________ LIKE in der WHERE Klausel________________________________________________ 53 54 54 54 55 55 57 59 59 59 60 60 61 61 Erklärung eines Explain Plan ___________________________________________ 63 4.1. Operationen eines Explain Plan __________________________________________ 66 ______________________________________________________________________________________________________ Oracle Trace Seite 2 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] 4.1.1. 4.1.2. 4.1.3. 4.1.4. 4.1.5. 4.1.6. 4.1.7. 4.1.8. 4.1.9. 4.1.10. 4.1.11. 4.1.12. 4.1.13. 4.1.14. 4.1.15. 4.1.16. 4.1.17. 4.1.18. 4.1.19. 4.1.20. 4.1.21. 4.1.22. 4.1.23. 4.1.24. 4.1.25. 4.1.26. 4.1.27. 4.1.28. 4.1.29. 4.1.30. 4.2. TABLE ACCESS (FULL)_____________________________________________________ TABLE ACCESS (CLUSTER) _________________________________________________ TABLE ACCESS (HASH) ____________________________________________________ TABLE ACCESS (BY ROWID)________________________________________________ INDEX (RANGE SCAN) _____________________________________________________ INDEX (UNIQUE SCAN) ____________________________________________________ INDEX (FAST FULL SCAN) __________________________________________________ INDEX (FULL SCAN) _______________________________________________________ INDEX (FULL SCAN (MIN/MAX)) ____________________________________________ CONNECT BY ___________________________________________________________ FILTER_________________________________________________________________ MERGE JOIN____________________________________________________________ MERGE JOIN (OUTER) ___________________________________________________ MERGE JOIN (CARTESIAN) _______________________________________________ SORT (ORDER BY) ______________________________________________________ SORT (JOIN) ____________________________________________________________ SORT (AGGREGATE) ____________________________________________________ SORT (UNIQUE) _________________________________________________________ SORT (GROUP BY) ______________________________________________________ NESTED LOOPS (OUTER)_________________________________________________ NESTED LOOPS (ANTI) __________________________________________________ NESTED LOOPS (SEMI) __________________________________________________ CONCATENATION ______________________________________________________ INTERSECTION _________________________________________________________ MINUS _________________________________________________________________ UNION _________________________________________________________________ UNION-ALL_____________________________________________________________ VIEW __________________________________________________________________ NESTED LOOPS _________________________________________________________ HASH JOIN _____________________________________________________________ 66 66 67 73 74 75 75 77 77 77 77 78 78 78 80 80 80 80 80 81 81 81 81 81 81 81 81 81 82 84 INDEX RANGE SCAN _________________________________________________ 85 5. Bedeutung von Tabellen und Index Statistiken _____________________________ 88 6. Glossar______________________________________________________________ 89 6.1. Recursive calls ________________________________________________________ 89 6.2. DB_BLOCK_SIZE_____________________________________________________ 89 6.3. DB_FILE_MULTIBLOCK_READ_COUNT _______________________________ 89 6.4. Chained rows _________________________________________________________ 90 6.5. Cost _________________________________________________________________ 91 6.6. DB Block gets _________________________________________________________ 91 6.7. DB Block changes______________________________________________________ 91 6.8. Datenblöcke im Überblick_______________________________________________ 92 6.9. Extents _______________________________________________________________ 93 6.10. High Water Mark______________________________________________________ 93 6.11. User calls _____________________________________________________________ 93 ______________________________________________________________________________________________________ Oracle Trace Seite 3 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 6.12. Execute counts ________________________________________________________ 93 6.13. Distinct keys __________________________________________________________ 94 6.14. Cardinalität___________________________________________________________ 94 6.15. Rule _________________________________________________________________ 95 6.16. Choose _______________________________________________________________ 97 6.17. Treibende Tabelle_____________________________________________________ 100 6.18. Der Shared Pool Memory ______________________________________________ 105 6.19. Die SQL Area ________________________________________________________ 106 6.20. Library cache ________________________________________________________ 106 6.21. db file scattered read __________________________________________________ 107 6.22. db file sequential read _________________________________________________ 107 6.23. Buffer busy waits _____________________________________________________ 107 6.24. Free buffer waits _____________________________________________________ 108 6.25. Latch free ___________________________________________________________ 108 6.26. Log buffer space ______________________________________________________ 109 6.27. Log file switch________________________________________________________ 109 6.28. Parse _______________________________________________________________ 109 6.28.1. 6.28.2. 7. Hard Parse______________________________________________________________ 114 Soft Parse ______________________________________________________________ 117 Index ______________________________________________________________ 118 ______________________________________________________________________________________________________ Oracle Trace Seite 4 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] 1. Eine Trace Datei Auch wenn eine Oracleinstanz nur mittelmäßig konfiguriert wurde, sind diese Performance-Verluste geringer als die, welche durch SQL Statements auslöst werden können. Eine Vielzahl an Mißständen kann die Ursache sein. Ohne Hilfsmittel ist es fast ummöglich sie zu finden. Ein Hilfsmittel ist es, während das oder die Statements abgearbeitet werden von Oracle eine Tracedatei erstellen zu lassen In der Trace Datei werden alle Statements protokolliert, welche direkt angegebenen oder durch den Aufruf von Modulen (Packages, Proceduren oder Function) verarbeitet wurden, aber auch solche, welche implizit durch Oracle zur Ausführung gelangten. In einer solchen Datei werden zu jedem Statement zusätzlich Statistiken „eingefangen“, die für das Tuning von höchster Bedeutung sind. Trace Dateien werden immer im „user_dump_dest“ Verzeichnis der Oracle Instanz erzeugt. Das physikalische Verzeichnis, welches sich hinter dem Alias „user_dump_dest“ verbirgt, kann in der initSID.ora Datei direkt eingesehen werden, oder aber mit einem Select Statemenet ermittelt werden, wie es nachfolgend beschrieben ist. SELECT NAME, VALUE FROM SYS.V_$SYSTEM_PARAMETER WHERE name = 'user_dump_dest' Die Oracle Datenbank besitzt gewöhnlich für dieses Verzeichnis ein Schreibund Leserecht. Der User, welcher die Trace Datei später auswerten möchte verfügt nicht automatisch über diese Rechte! Dies ist gerade für Bertriebsysteme wie Unix zu berücksichtigen. Im Dateinamen einer Tracedatei ist die Session ID enthalten, mit der das Betriebssystem die Session eindeutig verwaltet hat. So ergibt sich als Beispiel eine Datei mit dem Namen „ora_488.trc“ im „user_dump_dest“ Verzeichnis. Das bedeutet, daß die Session (hier SQL*Plus) vom Betriebsystem unter der Nummer 488 geführt wurde. Nur wenn der initSID.ora Parameter TIMED_STATISTICS auf TRUE gesetzt oder die Session entsprechend geändert wurde (ALTER SESSION SET TIMED_STATISTICS=TRUE), entstehen in der Trace Datei zeitliche Statistiken. Ausführungsstatistiken sind in jedem Fall enthalten. Das Erzeugen von zeitlichen Statistiken, also Timed Statistics, führt zu Verlusten zwischen 1% und 5% der CPU Leistung. Zu unterscheiden ist, ob ______________________________________________________________________________________________________ Oracle Trace Seite 5 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] TIMED_STATISTICS für die gesamte Instanz gilt oder auf eine Session beschränkt ist. In einer Trace Datei können nicht mehrere Sessions protokolliert werden. Es gibt verschiedene Möglichkeiten eine Trace Datei zu erzeugen. Welche das im Einzelnen sind und worin sie sich unterscheiden wird nachfolgend beschrieben. Eine Trace Datei wird erst angelegt, wenn mindestens ein Statement bearbeitet wurde und nicht zu dem Zeitpunkt zu dem der Trace aktiviert wurde. 1.1. Session Trace Ein „session trace“ ist nur für eine Session möglich, wie sie zum Beispiel entsteht, wenn SQL*Plus gestartet wird. Ein solches Programm muß über die Möglichkeit verfügen, die Oracle Session zu verändern (ALTER SESSION...) ALTER SESSION SET SQL_TRACE = TRUE; Der Trace kann jedoch nur eingeschaltet werden, wenn der Oracle User, der mit der Datenbank verbunden ist, über die Berechtigung „ALTER SESSION“ verfügt. Erst wenn der Tracemodus beendet wurde, wird die Tracedatei wieder geschlossen. Entweder durch ein ALTER SESSION Statement wie nachfolgend beschrieben oder durch das Beenden der Session (Disconnect oder Programmende). ALTER SESSION SET SQL_TRACE = FALSE; Wird innerhalb der gleichen Session mehrmals ein Trace ein- und abgeschaltet, werden alle Trace Ergebnisse in der selben Datei gespeichert!! 1.2. Trace einer beliebigen Session Wenn das Einschalten eines „Session trace“ nicht möglich ist (z.B. weil die Verarbeitung schon läuft oder USER X einen Trace für USER Y erstellen möchte), bietet Oracle die Möglichkeit, den Trace mit hilfe einer Procedure eines Package zu starten. Der Aufruf lautet: ______________________________________________________________________________________________________ Oracle Trace Seite 6 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] sys.dbms_system.set_sql_trace_in_session( SID, serial#, What ); Es werden jedoch SID und SERIAL# benötigt, um die Procedure ausführen zu können, die mit folgenden Script ermittelt werden können. Unter dem Einsatz von CUT und PASTE erleichert die Prompt Ausgabe den Aufruf. REM ACCEPT UserName PROMPT 'Name des Oracle Users: '; SELECT osuser, username, status, sid, serial#, Terminal, program FROM v$session WHERE osuser like UPPER( '&UserName' ); PROMPT EXEC sys.dbms_system.set_sql_trace_in_session( SID, serial#, What ); Zu jeder SID, SERIAL# erfolgt noch die Angabe, ob der Trace gestartet (TRUE) oder beendet (FALSE) werden soll. Wird für die gleiche Session (SID und SERIAL#) mehrmals ein Trace ein- und abgeschaltet, werden alle Ergebnisse in der selben Datei gespeichert. Die Art ein Trace zu erzeugen ist auch dann erforderlich, wenn nicht eine gesamte Session, sondern nur ein Ausschnitt der Verarbeitung überwacht werden soll – zum Beispiel aus Zeit oder Plattenplatz Gründen. Der User, welche eine fremde Transaktion oder Session tracen möchte, muß über das Ausführungsrecht der Package DBMS_SYSTEM verfügen und zwar als direkten Grant; nicht über eine Role. 1.3. Probleme beim Erstellen einer Trace Datei • Werden Trace Dateien aus dem „user_dump_dest“ Verzeichnis gelöscht, ohne daß die Datenbank neu gestartet wird, werden keine neuen Tracefiles erzeugt. • Ein EXPLAIN PLAN wird z.B. in einer TKProf Ausgabe Datei nicht erstellt, obwohl die Angabe „explain=..“ für die Erstellung einer TKProf Ausgabedatei angegeben wurde, wenn die Tabelle PLAN_TABLE für den User, mit dem der Ausführungsplan erstellt werden soll, keine Berechtigung auf diese Tabelle hat, die Tabelle nicht existiert, oder ein anderes Format besitzt, als es von Oracle für die Datenbankversion vorausgesetzt wird. • Werden die Anzahl Rows in einer Trace- oder einer TKProf Datei nicht angezeigt, kann es daran liegen, daß ein Trace erzeugt wurde für ein Statement, welche schon über einen längeren Zeitraum lief. Auch ein ALTER SESSION... kann dazu führen, daß für vereinzelte Statements keine Rows angezeigt werden. Werden in mehreren Trace Dateien die ______________________________________________________________________________________________________ Oracle Trace Seite 7 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] Rows nicht angezeigt, hat sich die Instanz „verschluckt“ und es hilft nur ein reboot. 1.4. • Wurde der Trace noch nicht geschlossen (zum Beispiel ALTER SESSION SET SQL_TRACE = FALSE), werden für die TKProf Datei keine Rows angezeigt. • Laufzeit Statistiken (alle zeitlichen Statistiken) werden nicht ermittelt, wenn die überwachte Session im Modus TIMED_STATISTICS=FALSE arbeitet. • Eine Trace Datei entsteht nicht zu dem Zeitpunkt, zu dem der Trace eingeschaltet wurde, sondern erst, nachdem das erste Statement ausgeführt wurde. • Wird innerhalb einer Session ein Trace mehrmals ein und abgeschaltet, werden die Ergebnisse des Trace in die selbe Datei eingetragen. Es entsteht keine neue Trace Datei. Autotrace Autotrace ist eine Low Cost Version an Tuning Informationen zu gelangen unter SQL*Plus. Wesentlicher Unterschied liegt in den fehlenden Statistiken über das Statement. So werden werden nicht die Ausführungsdauer oder die bearbeiteten Rows angezeigt, welche für die Ergebnismenge bearbeitet werden mußten. Ein EXPLAIN PLAN und die Ausführungsstatistiken wie CONSISTENT GETS (gleichbedeutend den Query Werten einer TKProf Datei) sind aber vorhanden. Autotrace wird gestartet in SQL*Plus durch die Eingabe SET AUTOTRACE ON und bleibt solange aktiv, bis der Modus mit SET AUTOTRACE OFF wieder beendet oder die SQL*Plus Session geschlossen wird. Autotrace bewirkt, daß alle eingegebenen Statemets getraced werden und die Trace Ergebnisse direkt nach der Ausführung angezeigt werden. Autotrace liefert nur dann die gwünschten Ergebnisse wenn die Tabelle SYS.PLAN_TABLE existiert und der User über Schreib- und Leserechte für diese Tabelle verfügt. Weitere Berechtigungen, die erforderlich sind, stellt Oracle mit dem Script PLUSTRACE.SQL bereit. Hierbei handelt es sich um die Einrichtung einer Berechtigungsrolle „plustrace“ wie folgt: drop role plustrace; create role plustrace; grant select on v_$sesstat to plustrace; grant select on v_$statname to plustrace; grant select on v_$session to plustrace; ______________________________________________________________________________________________________ Oracle Trace Seite 8 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] grant plustrace to xxxx with admin option; Nachfolgend ein Beispiel für den Einsatz von AUTOTRACE: SQLL > set autotrace on SQLL > select * from dual; D X 1 row selected. real: 581 Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Optimizer=CHOOSE 1 0 TABLE ACCESS (FULL) OF 'DUAL' Statistics ---------------------------------------------------------0 recursive calls 4 db block gets 1 consistent gets 0 physical reads 0 redo size 175 bytes sent via SQL*Net to client 256 bytes received via SQL*Net from client 3 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed SQL > set autotrace off; ______________________________________________________________________________________________________ Oracle Trace Seite 9 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 2. Trace Datei und TKProf Ausgabe Der Inhalt einer Trace Datei ist nicht zuletzt wegen der Dateigröße sehr schwierig lesbar. Aus diesem Grunde liefert Oracle ein Tool aus, das eine Zusammenfassung aller Informationen aus einer Trace Datei erstellt und in einer beliebig zu benennenden Ausgabe Datei speichert. Aber die Beschreibung der einzelnen Informationen direkt in der Trace Datei ist nicht weniger von Bedeutung als die TKProf Ausgabe. Z.B. sind nur in einer Trace Datei alle Informationen über Ablaufreihenfolgen erkennbar, welche Cursor von welchen abhängig waren, wie groß die Arrays waren die für einen FETCH eines oder mehrerer Statements benutzt wurde und in welcher Häufigkeit der Aufruf des gleichen Statement durch Oracle erfolgt sind. 2.1. Das Trace File im Überblick Mit den ersten Zeilen werden die Randbedingungen beschreiben die vorlagen, als die Trace Datei erstellt wurde. Angaben über den Namen der Tracedatei, über Oracle und die Maschine, auf der die Oracle Instance läuft und die Beschreibung der Instanz selbst. Die Zeile mit der Session ID *** SESSION ID:(9.21) spiegelt die SID und SERIAL# wieder, wie sie auch in der V$SESSON View enthalten sind. Anhand dieser Session ID kann die Trace Datei eindeutig der Session zugeordnet werden, für die ein Trace erzeugt wurde. Nachdem der Name der Application angezeigt wurde, ist der erste Cursor mit PARSING IN CURSOR #1 .... ausgewiesen. Einige Zeilen weiter -diese werden zu einem späteren Zeitpunkt beschrieben- erscheint das Statement für diesen Cursor, dessen Ende durch END OF STMT angezeigt wird. Mit PARSING IN CURSOR #2 wird der zweite Cursor beschrieben der geöffnet wurde. Das an dieser Stelle die 2 und nicht noch einmal die 1 eingetragen ist, weist darauf hin, daß der erste Cursor noch nicht geschlossen war, als der zweite geöffnet wurde. D.h., immer dann, wenn zum vorherigen Cursor die laufende Nummer um eins erhöht wurde, ist der Cursor mit niedriger Nummer noch nicht geschlossen. Dieses Verhalten kann man recht gut für die beiden Einträge PARSING IN CURSOR #8 in nachfolgenden Tracefile erkennen. Die Höchste Nummer eines Cursor in der gesamten Trace Datei beschreibt, wieviele Cursor maximal zeitglich geöffnet waren. ______________________________________________________________________________________________________ Oracle Trace Seite 10 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] PARSING IN CURSOR #2 zeigt im folgen Auszug einen Cursor, der von Oracle selbst erzeugt wurde. Dump file /oracle/815/admin/ORCL/udump/ora_488.trc Oracle8i Enterprise Edition Release 8.1.5.0.2 - Production With the Partitioning and Java options PL/SQL Release 8.1.5.0.0 - Production ORACLE_HOME = /oracle/815 System name: Linux Node name: lxnotebook Release: 2.2.13 Version: #3 Tue Mar 14 15:07:16 MET 2000 Machine: i686 Instance name: ORCL Redo thread mounted by this instance: 1 Oracle process number: 8 Unix process pid: 488, image: oracle@lxnotebook (TNS V1-V3) *** 2000.10.31.08.19.10.887 *** SESSION ID:(9.21) 2000.10.31.08.19.10.886 APPNAME mod='SQL*Plus' mh=3669949024 act='' ah=4029777240 ===================== PARSING IN CURSOR #1 len=52 dep=0 uid=21 oct=47 lid=21 tim=2808754576 hv=4201917273 ad='24442580' begin dbms_output.get_lines(:lines, :numlines); end; END OF STMT PARSE #1:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=0,tim=2808754576 EXEC #1:c=1,e=41,p=0,cr=0,cu=0,mis=0,r=1,dep=0,og=4,tim=2808754617 *** 2000.10.31.08.19.28.354 ===================== PARSING IN CURSOR #2 len=275 dep=1 uid=0 oct=3 lid=0 tim=2808756323 hv=3559516504 ad='2453335c' select obj#,type#,ctime,mtime,stime,status,dataobj#,flags,oid$ from obj$ where owner#=:1 and name=:2 and namespace=:3 and(remoteowner=:4 or remoteowner is null and :4 is null)and(linkname=:5 or linkname is null and :5 is null)and(subname=:6 or subname is null and :6 is null) END OF STMT PARSE #2:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=1,og=0,tim=2808756323 EXEC #2:c=0,e=6,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=2808756329 FETCH #2:c=0,e=0,p=0,cr=3,cu=0,mis=0,r=1,dep=1,og=4,tim=2808756329 ===================== PARSING IN CURSOR #3 len=350 dep=1 uid=0 oct=3 lid=0 tim=2808756329 hv=2216582187 ad='245324b0' select ts#,file#,block#,nvl(bobj#,0),nvl(tab#,0),intcols,nvl(clucols,0),audit$,flags,pctfree$, pctused$,initrans,maxtrans,rowcnt,blkcnt,empcnt,avgspc,chncnt,avgrln,analyzetime, samplesize,cols,property,nvl(degree,1),nvl(instances,1),avgspc_flb,flbcnt,kernelcols,nv l(trigflag, 0),nvl(spare1,0),nvl(spare2,0),spare4,nvl(spare3,0) from tab$ where obj#=:1 END OF STMT PARSE #3:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=1,og=0,tim=2808756329 EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=2808756329 FETCH #3:c=0,e=1,p=1,cr=3,cu=0,mis=0,r=1,dep=1,og=4,tim=2808756330 ===================== PARSING IN CURSOR #4 len=685 dep=1 uid=0 oct=3 lid=0 tim=2808756331 hv=199702406 ad='24531ee4' select i.obj#,i.ts#,i.file#,i.block#,i.intcols,i.type#,i.flags, i.property,i.pctfree$,i.initrans,i.maxtrans,i.blevel,i.leafcnt,i.distkey, i.lblkkey,i.dblkkey,i.clufac,i.cols,i.analyzetime,i.samplesize,i.dataobj#, nvl(i.degree,1),nvl(i.instances,1),i.rowcnt,mod(i.pctthres$,256),i.indmethod#,i.trunccn t,nvl(c.unicols,0),nvl(c.deferrable#+c.valid#,0), nvl(i.spare1,i.intcols),i.spare4,spare2,decode(i.pctthres$,null,null, mod(trunc(i.pctthres$/256),256)) from ind$ i, (select enabled, min(cols) unicols, min(to_number(bitand(defer,1))) deferrable#, min(to_number(bitand(defer,4))) valid# from cdef$ where obj#=:1 and enabled > 1 group by enabled) c where i.obj#=c.enabled(+) and i.bo#=:1 END OF STMT PARSE #4:c=1,e=1,p=0,cr=0,cu=0,mis=1,r=0,dep=1,og=0,tim=2808756331 ______________________________________________________________________________________________________ Oracle Trace Seite 11 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] EXEC #4:c=1,e=5,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=2808756336 FETCH #4:c=0,e=0,p=0,cr=3,cu=0,mis=0,r=0,dep=1,og=4,tim=2808756336 ===================== PARSING IN CURSOR #5 len=250 dep=1 uid=0 oct=3 lid=0 tim=2808756336 hv=906438690 ad='2452b92c' select name,intcol#,segcol#,type#,length,nvl(precision#,0),decode(type#,2,nvl(scale,127/*MAXSB1MINAL*/),0),null$,fixedstorage,nvl(deflength,0),default$,rowid,col#,property , charsetid,charsetform,spare1,spare2 from col$ where obj#=:1 order by intcol# END OF STMT PARSE #5:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=1,og=0,tim=2808756336 EXEC #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=2808756336 FETCH #5:c=0,e=0,p=0,cr=3,cu=0,mis=0,r=1,dep=1,og=4,tim=2808756336 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=1,og=4,tim=2808756336 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=1,og=4,tim=2808756336 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=1,og=4,tim=2808756336 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=1,og=4,tim=2808756336 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=1,og=4,tim=2808756336 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=1,og=4,tim=2808756336 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=1,og=4,tim=2808756336 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=1,og=4,tim=2808756336 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=2808756336 ===================== PARSING IN CURSOR #6 len=69 dep=1 uid=0 oct=3 lid=0 tim=2808756336 hv=114078687 ad='24543e10' select con#,obj#,rcon#,enabled,nvl(defer,0) from cdef$ where robj#=:1 END OF STMT PARSE #6:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=1,og=0,tim=2808756336 EXEC #6:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=2808756336 FETCH #6:c=0,e=0,p=0,cr=1,cu=0,mis=0,r=0,dep=1,og=4,tim=2808756336 ===================== PARSING IN CURSOR #7 len=132 dep=1 uid=0 oct=3 lid=0 tim=2808756336 hv=1639440790 ad='24543658' select con#,type#,condlength,intcols,robj#,rcon#,match#,refact,nvl(enabled,0),rowid,cols,nvl(d efer,0),mtime from cdef$ where obj#=:1 END OF STMT PARSE #7:c=1,e=1,p=0,cr=0,cu=0,mis=1,r=0,dep=1,og=0,tim=2808756337 EXEC #7:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=2808756337 FETCH #7:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=0,dep=1,og=4,tim=2808756337 ===================== PARSING IN CURSOR #8 len=190 dep=1 uid=0 oct=3 lid=0 tim=2808756337 hv=4059714361 ad='2454e914' select type#,blocks,extents,minexts,maxexts,extsize,extpct,user#,iniexts,NVL(lists,65535),NVL( groups,65535),cachehint,hwmincr, NVL(spare1,0) from seg$ where ts#=:1 and file#=:2 and block#=:3 END OF STMT PARSE #8:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=1,og=0,tim=2808756337 EXEC #8:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=2808756337 FETCH #8:c=0,e=0,p=0,cr=3,cu=0,mis=0,r=1,dep=1,og=4,tim=2808756337 STAT #8 id=1 cnt=1 pid=0 pos=0 obj=14 op='TABLE ACCESS CLUSTER SEG$ ' STAT #8 id=2 cnt=1 pid=1 pos=1 obj=9 op='INDEX UNIQUE SCAN ' ===================== PARSING IN CURSOR #8 len=128 dep=1 uid=0 oct=3 lid=0 tim=2808756339 hv=2554239735 ad='2441ba94' select u.name,o.name from obj$ o,user$ u,trigger$ t where t.baseobject=:1 and t.obj#=o.obj# and o.owner#=u.user# order by o.obj# END OF STMT PARSE #8:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=1,og=0,tim=2808756339 EXEC #8:c=1,e=1,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=2808756340 FETCH #8:c=0,e=0,p=0,cr=1,cu=0,mis=0,r=0,dep=1,og=4,tim=2808756340 STAT #8 id=1 cnt=0 pid=0 pos=0 obj=0 op='SORT ORDER BY ' STAT #8 id=2 cnt=0 pid=1 pos=1 obj=0 op='NESTED LOOPS ' STAT #8 id=3 cnt=1 pid=2 pos=1 obj=0 op='NESTED LOOPS ' STAT #8 id=4 cnt=1 pid=3 pos=1 obj=74 op='TABLE ACCESS BY INDEX ROWID TRIGGER$ ' STAT #8 id=5 cnt=1 pid=4 pos=1 obj=119 op='INDEX RANGE SCAN ' STAT #8 id=6 cnt=0 pid=3 pos=2 obj=18 op='TABLE ACCESS BY INDEX ROWID OBJ$ ' STAT #8 id=7 cnt=0 pid=6 pos=1 obj=33 op='INDEX UNIQUE SCAN ' ______________________________________________________________________________________________________ Oracle Trace Seite 12 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] STAT #8 id=8 cnt=0 pid=2 pos=2 obj=22 op='TABLE ACCESS CLUSTER USER$ ' STAT #8 id=9 cnt=0 pid=8 pos=1 obj=11 op='INDEX UNIQUE SCAN ' EXEC #2:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=2808756340 FETCH #2:c=0,e=0,p=0,cr=3,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756340 EXEC #3:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=2808756340 FETCH #3:c=0,e=0,p=0,cr=3,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756340 ===================== PARSING IN CURSOR #9 len=190 dep=2 uid=0 oct=3 lid=0 tim=2808756340 hv=4059714361 ad='2454e914' select type#,blocks,extents,minexts,maxexts,extsize,extpct,user#,iniexts,NVL(lists,65535),NVL( groups,65535),cachehint,hwmincr, NVL(spare1,0) from seg$ where ts#=:1 and file#=:2 and block#=:3 END OF STMT PARSE #9:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=2808756340 EXEC #9:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=2808756340 FETCH #9:c=0,e=0,p=0,cr=3,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756340 STAT #8 id=1 cnt=1 pid=0 pos=0 obj=14 op='TABLE ACCESS CLUSTER SEG$ ' STAT #8 id=2 cnt=1 pid=1 pos=1 obj=9 op='INDEX UNIQUE SCAN ' EXEC #4:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=2808756342 FETCH #4:c=0,e=0,p=0,cr=6,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756342 ===================== PARSING IN CURSOR #9 len=56 dep=2 uid=0 oct=3 lid=0 tim=2808756342 hv=4195740643 ad='24523c70' select pos#,intcol#,col#,spare1 from icol$ where obj#=:1 END OF STMT PARSE #9:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=2,og=0,tim=2808756342 EXEC #9:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=2808756342 FETCH #9:c=0,e=0,p=0,cr=3,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756342 FETCH #9:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756342 FETCH #9:c=0,e=0,p=0,cr=1,cu=0,mis=0,r=0,dep=2,og=4,tim=2808756342 FETCH #4:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756342 EXEC #9:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=2808756342 FETCH #9:c=0,e=0,p=0,cr=3,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756342 FETCH #9:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756342 FETCH #9:c=0,e=0,p=0,cr=1,cu=0,mis=0,r=0,dep=2,og=4,tim=2808756342 FETCH #4:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=2808756342 EXEC #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=2808756342 FETCH #5:c=0,e=0,p=0,cr=3,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756342 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756342 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756342 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756342 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756342 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756342 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756342 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756343 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756343 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756343 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756343 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756343 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756343 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756343 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756343 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756343 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756343 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756343 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756343 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756343 FETCH #5:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=2808756343 ===================== PARSING IN CURSOR #10 len=116 dep=2 uid=0 oct=3 lid=0 tim=2808756343 hv=189272129 ad='2452e4dc' select o.owner#,o.name,o.namespace,o.remoteowner,o.linkname,o.subname,o.dataobj#,o.flags from obj$ o where o.obj#=:1 END OF STMT PARSE #10:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=2,og=0,tim=2808756343 EXEC #10:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=2808756343 FETCH #10:c=0,e=0,p=0,cr=3,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756343 ______________________________________________________________________________________________________ Oracle Trace Seite 13 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] EXEC #10:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=2,og=4,tim=2808756343 FETCH #10:c=0,e=0,p=0,cr=3,cu=0,mis=0,r=1,dep=2,og=4,tim=2808756343 ===================== PARSING IN CURSOR #8 len=198 dep=1 uid=0 oct=3 lid=0 tim=2808756343 hv=1854821847 ad='24416dbc' select bucket_cnt, row_cnt, cache_cnt, null_cnt, timestamp#, sample_size, minimum, maximum, distcnt, lowval, hival, density, col#, spare1, spare2, avgcln from hist_head$ where obj#=:1 and intcol#=:2 END OF STMT PARSE #8:c=1,e=3,p=1,cr=36,cu=1,mis=1,r=0,dep=1,og=0,tim=2808756343 EXEC #8:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=2808756343 FETCH #8:c=0,e=1,p=3,cr=3,cu=0,mis=0,r=1,dep=1,og=4,tim=2808756344 ===================== PARSING IN CURSOR #1 len=63 dep=0 uid=21 oct=6 lid=21 tim=2808756344 hv=2216434206 ad='2443fc10' update LAMBERTZ.ADRESSEN set name='Meier' where name = 'Meier' END OF STMT PARSE #1:c=5,e=21,p=5,cr=58,cu=1,mis=1,r=0,dep=0,og=4,tim=2808756344 *** 2000.10.31.08.19.46.237 EXEC #1:c=440,e=1767,p=15276,cr=15275,cu=1958,mis=0,r=1903,dep=0,og=4,tim=2808758111 STAT #1 id=1 cnt=1 pid=0 pos=0 obj=0 op='UPDATE ADRESSEN ' STAT #1 id=2 cnt=1904 pid=1 pos=1 obj=3711 op='TABLE ACCESS FULL ADRESSEN ' ===================== PARSING IN CURSOR #1 len=52 dep=0 uid=21 oct=47 lid=21 tim=2808758187 hv=4201917273 ad='24442580' begin dbms_output.get_lines(:lines, :numlines); end; END OF STMT PARSE #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=2808758187 EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=1,dep=0,og=4,tim=2808758187 ===================== PARSING IN CURSOR #1 len=9 dep=0 uid=21 oct=45 lid=21 tim=2808758252 hv=3749013834 ad='2440cf1c' rollback END OF STMT PARSE #1:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=4,tim=2808758252 XCTEND rlbk=1, rd_only=0 ===================== PARSING IN CURSOR #2 len=275 dep=1 uid=0 oct=3 lid=0 tim=2808758259 hv=570441826 ad='24581ca4' select name,online$,contents$,undofile#,undoblock#,blocksize,dflmaxext,dflinit,dflincr,dflextp ct,dflminext, dflminlen, owner#,scnwrp,scnbas, NVL(pitrscnwrp, 0), NVL(pitrscnbas, 0), dflogging, bitmapped, inc#, flags, plugged, NVL(spare1,0), NVL(spare2,0) from ts$ where ts#=:1 END OF STMT PARSE #2:c=0,e=0,p=0,cr=0,cu=0,mis=1,r=0,dep=1,og=0,tim=2808758259 EXEC #2:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=1,og=4,tim=2808758259 FETCH #2:c=0,e=0,p=0,cr=2,cu=0,mis=0,r=1,dep=1,og=4,tim=2808758259 STAT #2 id=1 cnt=1 pid=0 pos=0 obj=16 op='TABLE ACCESS CLUSTER TS$ ' STAT #2 id=2 cnt=1 pid=1 pos=1 obj=7 op='INDEX UNIQUE SCAN ' EXEC #1:c=9,e=142,p=0,cr=22,cu=3830,mis=0,r=0,dep=0,og=4,tim=2808758394 *** 2000.10.31.08.20.02.044 2.1.1. Beschreibung der Ergebnisse in einer Trace Datei Wie im vorherigen Kapitel zu sehen war sind in einer Trace Datei eine Kürzel und Kennungen benutzt worden, die nachfolgend beschrieben werden. PARSE - Statistiken zum erfolgten Parsen ______________________________________________________________________________________________________ Oracle Trace Seite 14 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] EXEC - Statistiken zu einem INSERT, UPDATE oder DELETE FETCH - Statistiken zu einem SELECT Zu jedem einzelnen Vorgang werden aufgelistet: c CPU Einsatz in 1/100 Sekunden (CPU) e Gesamte Verarbeitungzeit in 1/100 Sekunden der entsprechenden Operation (elapsed) p Physikalische Disk Zugriffe auf dem Speichermedium (disk) cr Anzahl Consistent Gets (Query). cu Anzahl Current Blocks (Current). r Anzahl Rows, die eine Operation als Result geliefert hat. Für jedes FETCH werden z.B die Rows angegeben, die mit einem Fetch gelesen werden (Rows). Der Zähler eines Cursors (PARSING IN CURSOR #1) in einem *.trc File wird immer doppelt vergeben, wenn der Cursor geschlossen wurde bevor ein neuer erstellt wird. Für die wiederholte Ausführung folgender Statements select count(*) from adr_name; select count(*) from adr_name; Select count(*) from adr_name; zeigt sich das entsprechende Trace (*.trc) File: ===================== PARSING IN CURSOR #1 len=30 dep=0 uid=21 oct=3 lid=21 tim=2827937218 hv=3851129330 ad='24421058' select count(*) from adr_name END OF STMT PARSE #1:c=6,e=27,p=2,cr=40,cu=0,mis=1,r=0,dep=0,og=4,tim=2827937218 EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=2827937223 FETCH #1:c=15,e=121,p=946,cr=945,cu=4,mis=0,r=1,dep=0,og=4,tim=2827937344 STAT #1 id=1 cnt=1 pid=0 pos=0 obj=0 op='SORT AGGREGATE ' STAT #1 id=2 cnt=280073 pid=1 pos=1 obj=3717 op='TABLE ACCESS FULL ADR_NAME ' ===================== PARSING IN CURSOR #1 len=30 dep=0 uid=21 oct=3 lid=21 tim=2827937612 hv=3851129330 ad='24421058' select count(*) from adr_name END OF STMT PARSE #1:c=0,e=1,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=2827937612 EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=2827937616 FETCH #1:c=19,e=36,p=0,cr=945,cu=4,mis=0,r=1,dep=0,og=4,tim=2827937652 STAT #1 id=1 cnt=1 pid=0 pos=0 obj=0 op='SORT AGGREGATE ' STAT #1 id=2 cnt=280073 pid=1 pos=1 obj=3717 op='TABLE ACCESS FULL ADR_NAME ' ______________________________________________________________________________________________________ Oracle Trace Seite 15 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] ===================== PARSING IN CURSOR #1 len=30 dep=0 uid=21 oct=3 lid=21 tim=2827938903 hv=2776197289 ad='243fcd50' Select count(*) from adr_name END OF STMT PARSE #1:c=0,e=1,p=0,cr=0,cu=0,mis=1,r=0,dep=0,og=4,tim=2827938903 EXEC #1:c=0,e=0,p=0,cr=0,cu=0,mis=0,r=0,dep=0,og=4,tim=2827938907 FETCH #1:c=18,e=18,p=0,cr=945,cu=4,mis=0,r=1,dep=0,og=4,tim=2827938925 STAT #1 id=1 cnt=1 pid=0 pos=0 obj=0 op='SORT AGGREGATE ' STAT #1 id=2 cnt=280073 pid=1 pos=1 obj=3717 op='TABLE ACCESS FULL ADR_NAME' ===================== 2.2. Erstellen einer TKProf Datei Die Übesetzung einer *trc Datei mit dem Tool TKProf erfolgt: tkprof ora_488.trc ora_488.out explain=user/passwort@SID sys=no Es handelt sich um die *trc aus dem vorherigen Kapitel. Somit können Analogien recht gut erkannt werden. Die TKProf Datei hat folgendes Aussehen: TKPROF: Release 8.1.5.0.2 - Production on Tue Oct 31 08:25:18 2000 (c) Copyright 1999 Oracle Corporation. All rights reserved. Trace file: ora_488.trc Sort options: default ******************************************************************************** count = number of times OCI procedure was executed cpu = cpu time in seconds executing elapsed = elapsed time in seconds executing disk = number of physical reads of buffers from disk query = number of buffers gotten for consistent read current = number of buffers gotten in current mode (usually for update) rows = number of rows processed by the fetch or execute call ******************************************************************************** begin dbms_output.get_lines(:lines, :numlines); end; call count ------- -----Parse 2 Execute 2 Fetch 0 ------- -----total 4 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 0.01 0.41 0 0 0 0.00 0.00 0 0 0 -------- ---------- ---------- ---------- ---------0.01 0.41 0 0 0 rows ---------0 2 0 ---------2 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) ******************************************************************************** update LAMBERTZ.ADRESSEN set name='Meier' where ______________________________________________________________________________________________________ Oracle Trace Seite 16 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] name = 'Meier' call count ------- -----Parse 1 Execute 1 Fetch 0 ------- -----total 2 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.02 0 58 0 4.40 17.67 15276 15275 1958 0.00 0.00 0 0 0 -------- ---------- ---------- ---------- ---------4.40 17.69 15276 15333 1958 rows ---------0 1903 0 ---------1903 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) Rows ------1 1904 Row Source Operation --------------------------------------------------UPDATE ADRESSEN TABLE ACCESS FULL ADRESSEN Rows ------0 1 1904 Execution Plan --------------------------------------------------UPDATE STATEMENT GOAL: CHOOSE UPDATE OF 'ADRESSEN' TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADRESSEN' ******************************************************************************** rollback call count ------- -----Parse 1 Execute 1 Fetch 0 ------- -----total 2 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 0.09 1.42 0 20 3830 0.00 0.00 0 0 0 -------- ---------- ---------- ---------- ---------0.09 1.42 0 20 3830 rows ---------0 0 0 ---------0 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) ******************************************************************************** begin sys.dbms_system.set_sql_trace_in_session( 9,21 ,false ); end; call count ------- -----Parse 1 Execute 1 Fetch 0 ------- -----total 2 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 0.00 0.00 0 0 0 0.00 0.00 0 0 0 -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 rows ---------0 1 0 ---------1 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) ******************************************************************************** OVERALL TOTALS FOR ALL NON-RECURSIVE STATEMENTS call count ------- -----Parse 5 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.02 0 58 0 rows ---------0 ______________________________________________________________________________________________________ Oracle Trace Seite 17 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] Execute 5 Fetch 0 ------- -----total 10 4.50 19.50 15276 15295 5788 0.00 0.00 0 0 0 -------- ---------- ---------- ---------- ---------4.50 19.52 15276 15353 5788 1906 0 ---------1906 Misses in library cache during parse: 4 OVERALL TOTALS FOR ALL RECURSIVE STATEMENTS call count ------- -----Parse 13 Execute 19 Fetch 54 ------- -----total 86 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.03 0.05 1 30 1 0.02 0.12 0 0 0 0.00 0.02 4 60 0 -------- ---------- ---------- ---------- ---------0.05 0.19 5 90 1 rows ---------0 0 45 ---------45 Misses in library cache during parse: 12 5 user SQL statements in session. 13 internal SQL statements in session. 18 SQL statements in session. 1 statement EXPLAINed in this session. ******************************************************************************** Trace file: ora_488.trc Trace file compatibility: 7.03.02 Sort options: default 1 5 13 18 16 1 207 2.3. session in tracefile. user SQL statements in trace file. internal SQL statements in trace file. SQL statements in trace file. unique SQL statements in trace file. SQL statements EXPLAINed using schema: LAMBERTZ.prof$plan_table Default table was used. Table was created. Table was dropped. lines in trace file. Interpretieren einer TKProf Datei Auch wenn es sich bei der TKProf Ausgabe Datei um eine Zusammenfassung der Trace Ergebnisse der *trc Datei handelt werden noch recht viele Einzelinformationen geliefert. Trace file: ora_488.trc Sort options: default ******************************************************************************** count = number of times OCI procedure was executed cpu = cpu time in seconds executing elapsed = elapsed time in seconds executing disk = number of physical reads of buffers from disk query = number of buffers gotten for consistent read current = number of buffers gotten in current mode (usually for update) rows = number of rows processed by the fetch or execute call ******************************************************************************** ______________________________________________________________________________________________________ Oracle Trace Seite 18 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Im Vorspann der Ausgabe werden die ursprüngliche Trace Datei genannt und das Sortierkriterium, mit welchem die Datei erstellt wurde. 2.3.1. Darstellung der Ergebnisse in einer TKProf Datei Nachdem ein Statement oder der Aufruf einer PL/SQL Routine aufgeführt wurde, erfolgt die Darstellung der Statistik zu dieser SQL Ausführung in Form einer Matrix. call count ------- -----Parse 1 Execute 1 Fetch 0 ------- -----total 2 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.02 0 58 0 4.40 17.67 15276 15275 1958 0.00 0.00 0 0 0 -------- ---------- ---------- ---------- ---------4.40 17.69 15276 15333 1958 rows ---------0 1903 0 ---------1903 Die Matrix besteht im Wesentlichen aus den Zeilen „Parse“, „Execute“, „Fetch“ und „total“. Die Spalten zeigen die eigentlichen Statistiken der jeweiligen Operation. „Execute“ Werte fallen nur bei INSERT, UPDATE oder DELETE an und SELECT Statements verursachen „Fetch“ Werte. Grundsätzlich werden drei Arten Operationen unterschieden und protokolliert: Parse entstehen immer bei der Ausführung eines Statement. Die genaue Beschreinbung zum Prase ist dem Anhang zu entnehmen. Execute Werte zeigen die Anzahl der Ausführungen des Statements. Für SELECT Statements kann die Anzahl „Execute“ von der „Fetch“ abweichen. Für explizite Aufrufe ist die Anzahl Parse und Execute identisch. ************************************************************************ SELECT FROM , , , WHERE AND AND AND /*+ FIRST_ROWS */ plz.plz, ort.ort ADR_ADRESSEN adr ADR_NAME name ADR_Ort ort ADR_plz plz name.ID = adr.NAME_ID plz.ID = adr.PLZ_ID ort.ID = adr.ORT_ID name.name = 'Meier' call count ------- -----Parse 1 Execute 1 Fetch 595 cpu elapsed disk query current ------- --------- ------- ---------- ---------0.01 0.01 0 0 0 0.00 0.00 0 0 0 51.60 77.51 14084 3007636 4 rows ------0 0 1784 ______________________________________________________________________________________________________ Oracle Trace Seite 19 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] ------- ------ ------- --------- ------- ---------- ---------- ------total 597 51.61 77.52 14084 3007636 4 1784 ************************************************************************ Für das Beispiel wurde das Statement einmalig in SQL*Plus abgesetzt. Die wiederholte Ausführung des selben Statements, führt zur Verdopplung der „count“ Ergebnisse. ************************************************************************ call count ------- -----Parse 2 Execute 2 Fetch 1190 ------- -----total 1194 cpu elapsed disk query current ------- --------- ------- ---------- ---------... ... ... ------- --------- ------- ---------- ---------... rows ------- ------- ************************************************************************ Das folgende Beispiel zeigt Statements aus einem Cursor. den Aufruf eines SELECT ************************************************************************ declare v_plz NUMBER( 7 ); begin for cur_rec in( SELECT plz_id FROM adr_adressen WHERE rownum < 21 )loop SELECT plz INTO v_plz FROM adr_plz WHERE id=cur_rec.plz_id; end loop; end; call count ------- -----Parse 1 Execute 1 Fetch 0 ------- -----total 2 cpu elapsed disk query current -------- ---------- ------- ------- ---------0.01 0.01 0 0 0 0.00 0.01 0 0 0 0.00 0.00 0 0 0 -------- ---------- ------- ------- ---------0.01 0.02 0 0 0 rows -------0 1 0 -------1 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) ************************************************************************ SELECT PLZ_ID FROM ADR_ADRESSEN call count ------- -----Parse 1 WHERE ROWNUM < 21 cpu elapsed disk query current ------ --------- ------- -------- ---------0.00 0.00 0 0 0 rows -------0 ______________________________________________________________________________________________________ Oracle Trace Seite 20 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Execute 1 Fetch 21 ------- -----total 23 0.00 0.00 0 0 0 0.00 0.00 0 20 4 ------ --------- ------- -------- ---------0.00 0.00 0 20 4 0 20 -------20 ************************************************************************ SELECT PLZ FROM ADR_PLZ WHERE ID = :b1 call count ------- -----Parse 1 Execute 20 Fetch 20 ------- -----total 41 cpu elapsed disk query current -------- ---------- ------- ------- ---------0.00 0.00 0 0 0 0.00 0.00 0 0 0 0.03 0.06 2 40 80 -------- ---------- ------- ------- ---------0.03 0.06 2 40 80 rows -------0 0 20 -------20 ************************************************************************ Angaben größer 0 (außer der Spalte count) sind auf das Speichern von Zwischenergebnissen in der SGA zurück zu führen. Es werden in der Execute Zeile keine Werte für „disk“, „query“, „current“ oder „rows“ angezeigt. Deutlich zu sehen, im folgenden Beispiel für den Einsatz der Funktion MAX. ************************************************************************ SELECT MAX( name ) FROM adr_name call count ------- -----Parse 1 Execute 1 Fetch 1 ------- -----total 3 cpu elapsed disk query current -------- ---------- ------- ------- ---------0.00 0.00 0 0 0 0.00 0.01 0 0 0 0.00 0.08 3 3 0 -------- ---------- ------- ------- ---------0.00 0.09 3 3 0 rows ------0 0 1 ------1 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) Rows ------1 1 Row Source Operation --------------------------------------------------SORT AGGREGATE INDEX FULL SCAN (MIN/MAX) (object id 7063) ************************************************************************ Ein weiteres Beispiel zeigt, daß Oracle für die Ausführung eines Statement mit einem SubSelect (nicht correlated) eine temporäre Tabelle erstellt. ______________________________________________________________________________________________________ Oracle Trace Seite 21 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] ************************************************************************ SELECT * FROM adr_adressen WHERE EXISTS( SELECT '#' FROM adr_plz ) call count cpu elapsed disk query current rows ------- ------ ------- ---------- ------- -------- ---------- -------Parse 1 0.00 0.00 0 0 0 0 Execute 1 0.00 0.01 0 1 4 0 Fetch 32 0.01 0.00 0 32 4 96 ------- ------ ------- ---------- ------- -------- ---------- -------total 34 0.01 0.01 0 33 8 96 ************************************************************************ Fetch Werte entstehen ausschließlich für SELECT Anforderungen. Also nicht für die Ausführung eines UPDATE, DELETE oder INSERT. Zu jeder einzelnen Operation werden in der Matrix in Spalten aufgelistet: Count Aufzählung, wie häufig eine entsprechende Operation ausgeführt wurde CPU CPU Einsatz in Sekunden. Elapsed Gesamte Verarbeitungzeit der (parse, execute, fetch oder total). Disk Physikalische Zugriffe auf das Speichermedium (Festplatte). Query besitzt die gleiche Bedeutung wie CONSISTENT GETS. Also die Bereitstellung von konsistenten Datenblöcken für die Bearbeitung eines Statements. Die Daten stammen aus dem DB_BUFFER (oder der Festplatte, wenn die Daten schon geflasht wurden) oder als Rollback-Information aus dem Buffer oder von der Festplatte. entsprechenden Operation Query Werte für eine „Parse“ Operation sind auf das Lesen von Data Dictonary Informationen zurückzuführen. Für ein SubSELECT (nicht correlated) innerhalb einer WHERE Klausel (vorausgesetzt ORACLE hat das Statement beim Parse nicht in einen Join überführt) weisen die Spalten „Query“ und „Current“ Werte aus (also nicht nur „fetch“ oder „execute“). Ursache ist, daß ORACLE die Ergebnisse des SubSELECT in eine temporäre Tabelle speichert. Hiermit verbunden sind RECURSIVE CALLS. ______________________________________________________________________________________________________ Oracle Trace Seite 22 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] ************************************************************************ SELECT * FROM adr_adressen WHERE EXISTS( SELECT '#' FROM adr_plz ) call count ------- -----Parse 1 Execute 1 Fetch 32 ------- -----total 34 cpu elapsed disk query current ------- ---------- ------- -------- ---------0.00 0.00 0 0 0 0.00 0.00 0 1 4 0.01 0.00 0 32 4 ------- ---------- ------- -------- ---------0.01 0.00 0 33 8 rows -------0 0 96 -------96 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) Rows Row Source Operation ------- --------------------------------------------------96 FILTER 96 TABLE ACCESS FULL ADR_ADRESSEN 1 TABLE ACCESS FULL ADR_PLZ ************************************************************************ Es gibt Beispiele die zeigen, daß nicht zwingend CONSISTENT GETS für ein Statement entstehen. ************************************************************************ UPDATE ADR_ADRESSEN SET name = ´Mayer´ WHERE rowid = :a call count cpu elapsed disk query current rows ------- ------ ------ ---------- ------- ------- ---------- -------Parse 1 0.00 0.00 0 0 0 0 Execute 1 0.00 0.00 0 0 1 1 Fetch 0 0.00 0.00 0 0 0 0 ------- ------ ------ ---------- ------- ------- ---------- -------total 2 0.00 0.00 0 0 1 1 ************************************************************************ Für das UPDATE mit der WHERE Klausel auf eine Rowid werden keine „query“ Werte ausgewiesen! Auch für INSERT Operationen werden Query Werte ausgewiesen, die von Oracle für die Suche des nächsten freien Blocks anfallen. Selbst für Tabellen mit mehreren Millionen Datensätzen fällt dieser Wert kleiner 100 aus. Größere Werte weisen auf eine erhöhte Anzahl Extens (>10) hin und ein Reorg der entsprechenden Tabelle sollte angestrebt werden. Current werden beschrieben als CONSISTENT GETS Blöcke, die in den CURRENT Mode (lokal gekapselter Speicher) übernommen wurden, weil Oracle an diesen Blöcken Veränderungen ______________________________________________________________________________________________________ Oracle Trace Seite 23 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] vorgenommen hat oder neue Blöcke erzeugt wurden. CURRENT Werte treten z.B. auf, wenn durch Datenänderungen Korrekturen an der FREELIST in einem Block erforderlich wurden, oder durch die Reorganisation eines Index (bearbeiten der Header-, Leaf- oder Branch Blöcke eines B*Tree Index - ausgelöst durch INSERTs, UPDATEs oder DELETEs), usw. Current Werte treten aber auch auf, wenn viele Daten geändert oder gelöscht wurden, wodurch das gesamte Datenvolumen innerhalb eines Blocks den definierten PCTUSED unterschreitet. Es erfolgt ein Eintrag in die FREELIST, daß dieser Block wieder für die Aufnahme neuer Daten zur Verfügung steht. Für Änderungen einer Tabelle ohne Indizes liegt der „Current“ Wert bei einem Update unterhalb dem „Query“ Wert. Tabellen mit Indizes weisen grundsätzlich einen größeren „Current“ Wert gegenüber dem „Query“ Wert aus. Aber auch hier gibt es Ausnahmen. UPDATEs, für die keine Zeile zum Aktualisieren gefunden wurde, beträgt der Wert für „Execute“ 0. Somit ist auch ohne einen Execute Plan mit der Angabe von Rows zu erkennen, daß keine Zeile aktualisiert wurde. Für INSERTs hingegen werden keine „Current“ Werte ausgewiesen, wenn für den neuen Wert, der in die Tabelle eingetragen werden soll, das Einsortieren in den Index entfällt und statt dessen die neue Information einfach angehängt wird. CURRENT Werte treten nicht zwingend auf. Rows Anzahl Rows, welche für die einzelne Operation (Parse, Execute oder Fetch) als Ergebnis geliefert wurden. Fällt für die Zeile „Fetch“ die Angabe „count“ kleiner aus als der Wert für die ermittelten „Rows“, handelt es sich um einen „array fetch“. Array fetch hat den Vorteil, daß mehere Rows zu einem Paket zusammengafasst und nicht einzeln übertragen werden. call count ------- -----Parse 1 Execute 1 Fetch 595 ------- -----total 597 cpu elapsed disk query current ------- --------- ------- ---------- ---------0.01 0.01 0 0 0 0.00 0.00 0 0 0 51.60 77.51 14084 3007636 4 ------- --------- ------- ---------- ---------51.61 77.52 14084 3007636 4 rows ------0 0 1784 ------1784 Ein Array Fetch kann mit dem OCI (Oracle call interface) programmiert werden. Oracle Tools wie SQL*Plus oder SQL*Forms unterstützen Array fetch von hause aus. ______________________________________________________________________________________________________ Oracle Trace Seite 24 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Ausgwiesene Rows für einen Explain Plan zeigen die Anzahl Datensätze, welche aus den einzelnen Operationen hervorgingen. Für NESTED LOOPS oder ähnliche Gruppenfunktionen beschreibt die Angabe die Rows, welche aus der Mischoperation für die Weiterverarbeitung geliefert wurden. Hierzu ein Beispiel. ______________________________________________________________________________________________________ Oracle Trace Seite 25 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] ************************************************************************ SELECT FROM , , , WHERE AND AND AND /*+ FIRST_ROWS */ plz.plz, ort.ort ADR_ADRESSEN adr ADR_NAME name ADR_Ort ort ADR_plz plz name.ID = adr.NAME_ID plz.ID = adr.PLZ_ID ort.ID = adr.ORT_ID name.name = 'Meier' call count ------- -----Parse 1 Execute 1 Fetch 595 ------- -----total 597 cpu elapsed disk query current ------- --------- ------- ---------- ---------0.01 0.01 0 0 0 0.00 0.00 0 0 0 51.60 77.51 14084 3007636 4 ------- --------- ------- ---------- ---------51.61 77.52 14084 3007636 4 rows ------0 0 1784 ------1784 Misses in library cache during parse: 1 Optimizer goal: FIRST_ROWS Parsing user id: 21 (LAMBERTZ) Rows ------0 1784 1785 1785 1493661 1495444 2987320 3568 3568 1784 3568 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: HINT: FIRST_ROWS NESTED LOOPS NESTED LOOPS NESTED LOOPS TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ADRESSEN' TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_NAME' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_NAME_ID_IDX' (UNIQUE) TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_ORT' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_ORT_ID_IDX' (UNIQUE) TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_PLZ' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_PLZ_ID_IDX' (UNIQUE) ************************************************************************ 1785 Row’s bilden die Ergebnismenge aus dem ersten NESTED LOOP für die Bedingung „name.id = adr.name_id“. Auch wenn für die einzelnen Tabellen jeweils rund 1.5 Mio Rows gelesen wurden und den Index fast 3 Mio. Solche Zugriffe sind zu vermeiden. Innerhalb eines Cursors spiegelt die Row Angabe die Anzahl Datensätze wieder, welche für einen Fetch ermittelt wurden. Im folgenden Beispiel wird die Anzahl Rows in der Statistik mit 3 ausgewiesen und im Explain Plan wurde nur eine Row ermittelt, obwohl das Statement nur einmalig ausgeführt wurde. ______________________________________________________________________________________________________ Oracle Trace Seite 26 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] ****************************************************************************** -- Hier der Cursor in einem PL/SQL Block begin for i in( SELECT anrede FROM adr_anrede )loop INSERT into dual values ( '1' ); end loop; end; call count ------- -----Parse 1 Execute 1 Fetch 0 ------- -----total 2 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 0.00 0.01 0 0 0 0.00 0.00 0 0 0 -------- ---------- ---------- ---------- ---------0.00 0.01 0 0 0 rows ------0 1 0 ------1 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) ****************************************************************************** SELECT ANREDE FROM ADR_ANREDE call count ------- -----Parse 1 Execute 1 Fetch 3 ------- -----total 5 cpu elapsed disk query current -------- ---------- ---------- --------- --------0.00 0.03 0 3 0 0.00 0.00 0 0 0 0.00 0.04 2 3 9 -------- ---------- ---------- --------- --------0.00 0.07 2 6 9 rows --------0 0 3 --------3 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) (recursive depth: 1) Rows ------0 1 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: CHOOSE TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ANREDE' ****************************************************************************** INSERT INTO DUAL VALUES ( '1' ) call count ------- -----Parse 1 Execute 3 Fetch 0 ------- -----total 4 cpu elapsed disk query current -------- ---------- -------- --------- ---------0.00 0.00 0 0 0 0.00 0.00 0 2 7 0.00 0.00 0 0 0 -------- ---------- -------- --------- ---------0.00 0.00 0 2 7 rows -------0 3 0 -------3 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) (recursive depth: 1) Rows ------0 Execution Plan --------------------------------------------------INSERT STATEMENT GOAL: CHOOSE ****************************************************************************** ______________________________________________________________________________________________________ Oracle Trace Seite 27 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] Die Abweichungen sind sehr deutlich zu erkennen, wenn das Statement direkt in SQL*Plus ausgeführt wird. ****************************************************************************** SELECT ANREDE from ADR_ANREDE call count ------- -----Parse 1 Execute 1 Fetch 1 ------- -----total 3 cpu elapsed disk query current -------- ---------- -------- ---------- ---------0.00 0.00 0 0 0 0.00 0.00 0 0 0 0.00 0.00 0 2 4 -------- ---------- -------- ---------- ---------0.00 0.00 0 2 4 rows -------0 0 3 -------3 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) Rows ------0 3 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: CHOOSE TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ANREDE' ****************************************************************************** Es wurden mit einem ARRAY Fetch 3 Datensätze als Ergebnis geliefert. Auch die Anzahl Rows des Explain Plan wird nun mit 3 Rows ausgewiesen. Für dieses Beispiel ist die verfälsche Anzahl Rows des Explain Plan noch ersichtlich. Schwieriger wird es, wenn eine PL/SQL Procedure, Funktion aufgerufen wird. Grundsätzlich gilt: • Für die ausgewiesenen Statistiken eines Statements handelt es sich für die wiederholte Ausführung um eine Zusammenfassung von Einzelergebnissen. Dann sind die Ergebnisse durch die Anzahl der Ausführungen zu teilen, um die Durchschnittswerte für eine Ausführung zu erhalten. Disk Angaben sind hiervon ausgenommen. • Die Spalteninformationen zu den Fetch, Execute und Parse Angaben lassen eine erste Einschätzung zu, ob die Verarbeitung eines Statements überwiegend im Memory erfolgen konnte. Logical reads – physical reads (Query + Current) - Disk ______________________________________________________________________________________________________ Oracle Trace Seite 28 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] ------------------------------------------------------------- = -------------------------------------------------- Logical reads <= 99 % (Query + Current) Stellt sich ein Wert kleiner als 99% ein, liegt die Ursache entweder an der zu großen Anzahl Extents oder daran, daß der „Library Cache“ nicht ausreichend groß dimensioniert ist. Wenn die Maschine nicht über ausreichend Memory verfügt um den „Library Cache“ vergrößern zu können, wird man mit diesem Mißstand leben müssen. Wenn die Anzahl Disk I/O überschaubar bleibt, relativ zu dem „Query“ Wert, ist hier kein Handlungsbedarf. Außer, die Device’s sind extrem langsam. • Für die Anzahl Rows im EXPLAIN PLAN handelt es sich nicht um eine Zusammenfassung, sondern um die tatsächliche Anzahl Rows, welche für eine einmalige Ausführung ermittelt wurden. • Für eine „0.00“ Zeitangabe liegt die Verabeitungsdauer unter 1 / 100 Sekunde und wird nicht mehr erfaßt. • Häufig zu erkennen ist, daß die „elapsed“ Zeit geringer als die „CPU“ Zeit ausfällt. Ursache sind Rundungsdifferenzen bei der Summenbildung aus dem Trace File. Fakt ist, daß „elapsed“ immer über der „CPU“ Zeit liegt. Häufig anzutreffen, wenn eine große Anzahl „Executes“ oder „Fetch“ ausgewiesen ist. 2.3.2. Was bildet die Differenz zwischen CPU- und elapsed time Warum ein Statement mal schnell und mal langsam die Ergebnisse liefert, kann sicherlich an der gesamten Situation einer Datenbank Instanz liegen. So kann eine lang andauernde fremde Batchverarbeitung die eigene Verarbeitung ziemlich behindern. Aber auch Statements aus einer fremden Transaktion, welche große Datenmengen bearbeiten, behindern die eigene Transaktion. Solche „Behinderungen“ lassen sich meist nicht vermeiden. Anders verhält es sich, wenn für eine Foreign Key Tabelle ein Index für diesen Key fehlt. Dann erfolgt ein TABLE LOCK auf die Foreign Key Tabelle in dem Moment, für den ein UPDATE auf eine Datensatz der Primary Tabelle durchgeführt wird. Im Normalfall sperrt Oracle die Datensätze der Foreign Key Tabelle, welche dem Key der Primary Tabelle entsprechen, über den Foreign Key Index um die Datenintegrität zu gewährleisten. Fehlt nun der Foreign Key Index auf der Foreign Key Tabelle, kann Oracle die Sätze nicht eindeutig identifizieren, welche dem Primary Key entsprechen und sperrt (sicherheitshalber) die gesamte Tabelle. Dieser Zustand wird nur zum Problem, wenn zeitgleich ______________________________________________________________________________________________________ Oracle Trace Seite 29 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] mehere Anwender verschiedene Datensätze der Primary Tabelle ändern oder löschen wollen. Dann entsteht ein Wartezustand auf die Foreign Key Tabelle bis die Tabelle wieder freigegeben wird. Hier einmal grafisch die Lock-Situation: Primary Key Tabelle Foreign Key Tabelle Sperrung der gesamten Tabelle Primary Key Primary Key Foreign Key 4711 Noname 1 4711 ... 5813 Mueller 8 5813 ... Warten auf Sperre Anders, wenn zu dem Foreign Key Constraint ein Index vorhanden ist. Dann sperrt Oracle nur die Sätze, welche dem Primary Key entsprechen. Primary Key Tabelle Foreign Key Tabelle Primary Key 4711 Noname 5813 Mueller Satzsperre durch Index Primary Key Foreign Key 1 4711 ... 8 5813 ... ______________________________________________________________________________________________________ Oracle Trace Seite 30 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Weitere Ursachen für einen großen Unterschied zwischen „elapsed“ und „CPU“ können sein: Überforderte CPU Überfordertes Netzwerk Überforderte oder schlecht ausgelastete Festplatten (üngünstiges Striping, Tablespace ungünstig verteilt oder langsame Platten) Table locking (Konkurierende DML’s wie oben gezeigt) Schlecht gewählter DB_FILE_MULTIBLOCK_READ_COUNT Faktor, wodurch eine zu geringe Anzahl Datenblöcke mit einem Lesezugriff in den Memory gelesen werden. Wartezeiten im Bereich I/O sind die Folge. Aber auch eine zu große Anzahl kann dazu führen, daß der verfügbare Memory nicht ausreicht die Datenmengen aufzunehmen. Übermäßiges „flashen“ ist die Folge. Zu geringe Anzahl FREELISTs (Konkurierende DML’s) Zu geringe Anzahl INITTRANS einer Tabelle (Konkurierende DML’s) Block contention (Konkurierende DML’s) durch einen Parallelbetrieb, um die Gesamtlaufzeit einer Operation auf mehere Transaktionen aufzuteilen. Bereichsabgrenzungen bestimmter Daten (z.B. Vertragsnummer) werden zum Steuern der einzelnen Transaktionen eingesetzt. Gerade im Zugriff der Indexblöcke oder Datenblöcke entstehen Lockings dadurch, daß die Transaktion A an dem Block X Änderungen vornehmen muß (durch Insert, delete oder update), der von der anderen Transaktion gelesen werden soll. Genau zu dem Zeitpunkt hält Oracle eine Blocksperre und der Select muß warten. Latch waits ______________________________________________________________________________________________________ Oracle Trace Seite 31 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 3. So arbeitet Oracle Am besten zu verstehen sind EXPLAIN PLAN’s und Traceergebnisse, wenn bekannt ist, wie Oracle arbeitet. Grundsätzlich muß berücksichtigt werden, ob die Verarbeitug im RULE oder dem CHOOSE Mode erfolgt. 3.1. Auflösung eines Statement Nun soll einmal untersucht werden, wie Oracle ein Statement auflöst. Das folgende Statement hat die Aufgabe alle PLZ und ORTe auszugeben für alle Adressen, die dem Namen „Meier“ entsprechen. SELECT FROM , , , WHERE AND AND AND plz.plz, ort.ort ADR_ADRESSEN adr ADR_NAME name ADR_Ort ort ADR_plz plz name.ID = adr.NAME_ID plz.ID = adr.PLZ_ID ort.ID = adr.ORT_ID name.name = 'Meier'; Nun, die selektierten Attribute plz.PLZ und ort.ORT sind nicht in der Tabelle NAME enthalten, für die eine Einschränkung auf das Attribut „name.name“ vorliegt. Die folgende Darstellung zeigt die Abhängigkeiten der einzelnen Tabellen und verdeutlicht, wie mit dem bekannten Namen eine zugehörige PLZ und ORT ermittelt werden könnte. Name.name -> name.id -> adr.name_id -> adr.ort_id -> ort.id -> ort.ort Name.name -> name.id -> adr.name_id -> adr.plz_id -> plz.id -> plz.plz Wird unterstellt, daß Oracle auch dieses Vorgehen wählt, erfolgt im ersten Schritt die Ermittlung einer Teilmenge aus der Tabelle ADR_NAME, die der Bedingung „NAME = ‘Meier‘ “ entspricht (im Beispiel Statement entspricht das genau einem Datensatz). Ein DESCRIBE zeigt, daß die Tabelle ADR_NAME außer dem eigentlichen Namen noch eine ID beinhaltet. Somit ist für jeden gefundenen Namen dieser Tabelle auch die ID bekannt. In der Tabelle ADR_ADRESSEN existiert die gleiche ID unter dem Attributnamen NAME_ID. ______________________________________________________________________________________________________ Oracle Trace Seite 32 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] SQL > desc adr_name; Name Null? ------------------------------- -------ID NAME Type ---NUMBER(7) VARCHAR2(90) Somit ist die Bildung einer weiteren Teilmenge aus der Tabelle ADR_ADRESSEN möglich, welche der Bedingung „adr.name_id = name.id“ entspricht. Aus der Tabellendefinition ist ersichtlich, daß mit jeden Datensatz der Tabelle ADR_ADRESSEN auch eine PLZ_ID und ORT_ID geliefert wird. SQL > desc adr_adressen; Name Null? ------------------------------- -------ID ANREDE_ID VORNAME NAME_ID STRASSE PLZ_ID ORT_ID TELEFON VORWAHL_ID RUFNUMMER Type ---NUMBER(7) NUMBER(7) VARCHAR2(25) NUMBER(7) VARCHAR2(60) NUMBER(7) NUMBER(7) VARCHAR2(15) NUMBER(7) VARCHAR2(30) Sie referenzieren auf die ID’s der Tabellen ADR_PLZ und ADR_ORT, zu der jeweils die tatsächliche PLZ und der ORT gespeichert sind und bestimmen die Ergebnismenge. Soweit zur Theorie. Nachfolgend wird das Statement in verschiedenen Modi ausgeführt um zu sehen, wie sich Oracle tatsächlich verhält. Zu Beginn die Ausführung im RULE Mode, indem der entsprechende Hint eingesetzt wird. ****************************************************************************** SELECT FROM , , , WHERE AND AND AND /*+ RULE */ plz.plz, ort.ort ADR_ADRESSEN adr ADR_NAME name ADR_Ort ort ADR_plz plz name.ID = adr.NAME_ID plz.ID = adr.PLZ_ID ort.ID = adr.ORT_ID name.name = 'Meier' call count ------- -----Parse 1 Execute 1 Fetch 595 ------- -----total 597 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.01 0 0 0 0.00 0.00 0 0 0 127.60 165.63 11389 7483264 4 -------- ---------- ---------- ---------- ---------127.60 165.64 11389 7483264 4 rows ---------0 0 1784 ---------1784 ______________________________________________________________________________________________________ Oracle Trace Seite 33 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] Misses in library cache during parse: 1 Optimizer goal: RULE Parsing user id: 21 (LAMBERTZ) Rows ------0 1784 1493661 1493661 1493661 2987320 2987320 2987320 2987320 1784 2987320 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: HINT: RULE NESTED LOOPS NESTED LOOPS NESTED LOOPS TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ADRESSEN' TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_PLZ' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_PLZ_ID_IDX' (UNIQUE) TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_ORT' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_ORT_ID_IDX' (UNIQUE) TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_NAME' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_NAME_ID_IDX' (UNIQUE) ****************************************************************************** Execution Plan -----------------------------------------------------------------------------1.1 NESTED LOOPS 2.1 NESTED LOOPS 3.1 NESTED LOOPS 4.1 TABLE ACCESS FULL LAMBERTZ.ADR_ADRESSEN 4.2 TABLE ACCESS BY INDEX ROWID LAMBERTZ.ADR_PLZ 5.1 INDEX UNIQUE SCAN LAMBERTZ.ADR_PLZ_ID_IDX 3.2 TABLE ACCESS BY INDEX ROWID LAMBERTZ.ADR_ORT 4.1 INDEX UNIQUE SCAN LAMBERTZ.ADR_ORT_ID_IDX 2.2 TABLE ACCESS BY INDEX ROWID LAMBERTZ.ADR_NAME 3.1 INDEX UNIQUE SCAN LAMBERTZ.ADR_NAME_ID_IDX NESTED LOOPS<---NESTED LOOPS<---NESTED LOOPS<---TABLE ACCESS ^ ^ ^ FULL | | | ADR_ADRESSEN | | | | | | | | +---------------TABLE ACCESS<---INDEX | | BY INDEX ROW UNIQUE SCAN | | ADR_PLZ ADR_PLZ_ID_I | | | | | +---------------TABLE ACCESS<---INDEX | BY INDEX ROW UNIQUE SCAN | ADR_ORT ADR_ORT_ID_I | | +---------------TABLE ACCESS<---INDEX BY INDEX ROW UNIQUE SCAN ADR_NAME ADR_NAME_ID_ Der Ausführungsplan zeigt ein anderes Vorgehen als erwartet. ______________________________________________________________________________________________________ Oracle Trace Seite 34 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Die Ursache liegt im RULE Mode. RULE Mode heißt regelbasiert und eine jener Regeln ist, die Bearbeitung des Statement mit der Tabelle zu beginnen, die in der FROM Anordnung als letztes eingetragen ist. Somit wurde in dem Statement ADR_PLZ als treibende Tabelle bestimmt. Für diese Tabelle liegt aber keine Einschränkung vor. Somit werden alle Ergebnisse der Tabelle ADR_ADRESSEN mit denen aus der Tabelle ADR_PLZ verglichen. Ob der Zugriff auf die Tabelle ADR_PLZ mit dem Index ADR_PLZ_ID_IDX einen Vorteil für die Laufzeit darstellt, kann erst einmal nicht angenommen werden. Dieser Zugriff ist als schlecht anzusehen. Es wurden knapp 7,5 Mio CONSISTENT GETS (query) benötigt, die Ergebnismenge zu bestimmen! Wird nun entsprechend der Regel das Statement umgestellt und die treibene Tabelle ADR_NAME an das Ende der FROM Klausel eingetragen und die Einschränkung für diese Tabelle zu Beginn der WHERE Klausel, zeigt sich die Ausführung wie folgt. ****************************************************************************** SELECT FROM , , , WHERE AND AND AND /*+ RULE */ plz.plz, ort.ort ADR_ADRESSEN adr ADR_Ort ort ADR_plz plz ADR_NAME name name.name = 'Meier' name.ID = adr.NAME_ID plz.ID = adr.PLZ_ID ort.ID = adr.ORT_ID call count ------- -----Parse 1 Execute 1 Fetch 595 ------- -----total 597 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 0.00 0.00 0 0 0 51.39 64.80 10056 3007636 4 -------- ---------- ---------- ---------- ---------51.39 64.80 10056 3007636 4 rows ---------0 0 1784 ---------1784 Misses in library cache during parse: 1 Optimizer goal: RULE Parsing user id: 21 (LAMBERTZ) Rows ------0 1784 1785 1785 1493661 1495444 2987320 3568 3568 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: HINT: RULE NESTED LOOPS NESTED LOOPS NESTED LOOPS TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ADRESSEN' TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_NAME' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_NAME_ID_IDX' (UNIQUE) TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_PLZ' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_PLZ_ID_IDX' (UNIQUE) ______________________________________________________________________________________________________ Oracle Trace Seite 35 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 1784 3568 TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_ORT' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_ORT_ID_IDX' (UNIQUE) ****************************************************************************** Somit hat sich die Annahme von Beginn bestätigt, wie die Bearbeitung eines Statements durch Oracle erfolgt. Wurde der Hint RULE des ursprünglichen Statements gegen FIRST_ROWS ersetzt, zeigt die TKProf Ausgabe, daß aufgrund der Statistiken, über welche Oracle zu diesen Tabellen verfügt, das das Statement vor der Bearbeitung (im Parsing) so umgestellt wird, daß die Tabelle ADR_NAME zur treibenden Tabelle wurde. ****************************************************************************** SELECT FROM , , , WHERE AND AND AND /*+ FIRST_ROWS */ plz.plz, ort.ort ADR_ADRESSEN adr ADR_NAME name ADR_Ort ort ADR_plz plz name.ID = adr.NAME_ID plz.ID = adr.PLZ_ID ort.ID = adr.ORT_ID name.name = 'Meier' call count ------- -----Parse 1 Execute 1 Fetch 595 ------- -----total 597 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.01 0.01 0 0 0 0.00 0.00 0 0 0 51.48 64.83 10056 3007636 4 -------- ---------- ---------- ---------- ---------51.49 64.84 10056 3007636 4 rows ---------0 0 1784 ---------1784 Misses in library cache during parse: 1 Optimizer goal: FIRST_ROWS Parsing user id: 21 (LAMBERTZ) Rows ------0 1784 1785 1785 1493661 1495444 2987320 3568 3568 1784 3568 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: HINT: FIRST_ROWS NESTED LOOPS NESTED LOOPS NESTED LOOPS TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ADRESSEN' TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_NAME' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_NAME_ID_IDX' (UNIQUE) TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_ORT' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_ORT_ID_IDX' (UNIQUE) TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_PLZ' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_PLZ_ID_IDX' (UNIQUE) ****************************************************************************** ______________________________________________________________________________________________________ Oracle Trace Seite 36 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Somit kann angenommen werden, daß Oracle ein Statement im CHOOSE Mode vor der Ausführung den Regeln entsprechend umformatiert und erst dann das Result Set bildet! Das optimalste Vorgehen zeigt Oracle ohne Angabe eines Hint durch den Einsatz mehrerer HASH JOIN. SELECT FROM , , , WHERE AND AND AND plz.plz, ort.ort ADR_ADRESSEN adr ADR_NAME name ADR_Ort ort ADR_plz plz name.ID = adr.NAME_ID plz.ID = adr.PLZ_ID ort.ID = adr.ORT_ID name.name = 'Meier' call count ------- -----Parse 1 Execute 1 Fetch 595 ------- -----total 597 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.01 0.00 0 0 0 0.00 0.00 0 0 0 4.04 10.37 10056 13183 12 -------- ---------- ---------- ---------- ---------4.05 10.37 10056 13183 12 rows ---------0 0 1784 ---------1784 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) Rows ------0 1784 584 1784 296 1784 1 2 1493660 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: CHOOSE HASH JOIN TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_PLZ' HASH JOIN TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ORT' HASH JOIN TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_NAME' INDEX GOAL: ANALYZED (RANGE SCAN) OF 'ADR_NAME_IDX' (NON-UNIQUE) TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ADRESSEN' ****************************************************************************** In der folgenden erweiterten Darstellung des Ausführungsplan ist zu erkennen, daß ADR_NAME auch an dieser Stelle wieder zur treibenden Tabelle eingesetzt wurde. SELECT STATEMENT OPIMIZER=CHOOSE cost= 3068 Execution Plan ------------------------------------------------------------1.1 HASH JOIN 2.1 TABLE ACCESS FULL LAMBERTZ.ADR_PLZ 2.2 HASH JOIN 3.1 TABLE ACCESS FULL LAMBERTZ.ADR_ORT 3.2 HASH JOIN Cost --------3068 1 3066 1 3064 ______________________________________________________________________________________________________ Oracle Trace Seite 37 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 4.1 TABLE ACCESS BY INDEX ROWID LAMBERTZ.ADR_NAME 5.1 INDEX RANGE SCAN LAMBERTZ.ADR_NAME_IDX 4.2 TABLE ACCESS FULL LAMBERTZ.ADR_ADRESSEN 4 3 3016 HASH JOIN<------TABLE ACCESS ^ FULL | ADR_PLZ | | +---------------HASH JOIN<------TABLE ACCESS ^ FULL | ADR_ORT | | +---------------HASH JOIN<------TABLE ACCESS<---INDEX ^ BY INDEX ROW RANGE SCAN | ADR_NAME ADR_NAME_IDX | | +---------------TABLE ACCESS FULL ADR_ADRESSEN Die treibende Tabelle ADR_NAME verfügt über eine qualifizierte Einschränkung (name.name = 'Meier‘) und wird per Index angesprochen. Basierend auf diesem Datensatz werden alle weiteren Teilmengen über einen HASH JOIN verarbeitet. Liegt in der WHERE Klausel keine Einschränkung für auch nur eine Tabelle vor (ungeachtet der Join Verknüpfung), muß dennoch eine als treibende Tabelle bestimmt werden. Das ist dann die Tabelle mit den geringsten Datensätzen (laut Statistik. Wenn falsche Statistiken vorliegen, kann es zu einer Fehleinschätzung kommen). Diese Tabelle wird ggf. auch mit einem Index gelesen. Z.B. wenn für das Statement der Hint FIRST_ROWS angegeben wurde mit der Forderung, möglichst schnell die ersten Ergebnisse zu liefern. SELECT FROM , , , WHERE AND AND /*+ FIRST_ROWS */ plz.plz, ort.ort ADR_ADRESSEN adr ADR_NAME name ADR_Ort ort ADR_plz plz name.ID = adr.NAME_ID plz.ID = adr.PLZ_ID ort.ID = adr.ORT_ID Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Cost=2989450 Optimizer=HINT: FIRST_ROWS 1 0 NESTED LOOPS 2 1 NESTED LOOPS 3 2 NESTED LOOPS 4 3 TABLE ACCESS (FULL) OF 'ADR_ADRESSEN' 5 3 TABLE ACCESS (BY INDEX ROWID) OF 'ADR_ORT' ______________________________________________________________________________________________________ Oracle Trace Seite 38 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] 6 7 8 9 5 2 7 1 INDEX (UNIQUE SCAN) OF 'ADR_ORT_ID_IDX' TABLE ACCESS (BY INDEX ROWID) OF 'ADR_PLZ' INDEX (UNIQUE SCAN) OF 'ADR_PLZ_ID_IDX' INDEX (UNIQUE SCAN) OF 'ADR_NAME_ID_IDX' SQL > select count(*) from adr_adressen; COUNT(*) ---------1493660 1 row selected. real: 13868 SQL > select count(*) from adr_plz; COUNT(*) ---------584 1 row selected. real: 140 SQL > select count(*) from adr_ort; COUNT(*) ---------296 1 row selected. real: 140 SQL > select count(*) from adr_name; COUNT(*) ---------280073 Die Tabelle Ort ist mit 296 Rows die Kleinste aller beteiligten. Fehlt eine Einschränkung in der WHERE Klausel, um mit Zwischenergebnissen die nächsten Teilergebnisse des Join zu bestimmen, entsteht ein Kartesiches Produkt. Für das oben eingesetzte Statement wurden alle Join Verknüpfungen entfernt, so das nur noch eine Einschränkung auf die Tabelle ADR_NAME vorhanden ist. Der EXPLAIn PLAN zeigt die Auswirkungen. SELECT FROM , , , WHERE /*+ FIRST_ROWS */ plz.plz, ort.ort ADR_ADRESSEN adr ADR_Ort ort ADR_plz plz ADR_NAME name name.name = 'Meier'; Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Cost=885769941 Optimizer=HINT: FIRST_ROWS 1 0 MERGE JOIN (CARTESIAN) ______________________________________________________________________________________________________ Oracle Trace Seite 39 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 2 3 4 5 6 7 8 9 10 1 2 3 3 5 2 7 1 9 MERGE JOIN (CARTESIAN) MERGE JOIN (CARTESIAN) INDEX (RANGE SCAN) OF 'ADR_NAME_IDX' SORT (JOIN) TABLE ACCESS (FULL) OF 'ADR_ORT' SORT (JOIN) TABLE ACCESS (FULL) OF 'ADR_ADRESSEN' SORT (JOIN) TABLE ACCESS (FULL) OF 'ADR_PLZ' 3.2. Der Index Informationen einer Tabelle können sequentiell, also alle hintereinander, oder geziehlt per ROWID angesprochen werden. Das Heraussuchen eines Datensatz aus einer Tabelle über die ROWID ist die schnellste Art an die Information in einer Datentablle zu gelangen. Die Vorstellung, ein Index Zugriff ist immer gut, ist nicht richtig. Denn ein Index ist im Prinzip nichts anderes als eine Tabelle, in der Attribute vorgehalten werden, die der zugehörigen Tabelle entsprechen und zusätzlich einen Verweis auf den Datensatz der Tabelle, die Rowid. Das ein Index von seiner Struktur her schneller die gesuchten Daten findet ist sicherlich richtig, da ein Index nach einem anderen Muster Daten ablegt und auch wiederfindet. Ein Zeitverlust ergibt sich je nach Ergebnismenge des Index durch den Abgleich mit den Daten der Tabelle. Für Indizes werden an Blöcken mindestens gelesen: B*Tree Index 1-3 - RANGE SCAN, UNIQUE SCAN N - FAST FULL SCAN. Die Anzahl Datenblöcke wird gesteuert durch den Parameter DB_FILE_MULTIBLOCK_READ_COUNT der initSID.ora Datei 3.2.1. Bitmap Index 1 Hash Cluster 1 Ungünstiger CLUSTERING_INDEX Der CLUSTERING_INDEX ist eine Maßzahl für die Verteilung der sortierten Indexdaten zu der entsprechenden Daten der Tabelle. Im besten Fall entspricht der CLUSTERING_INDEX der Anzahl LEAF BLOCKS des Indizes, wogegen das schlechteste Ergebnis gleich der Anzahl Datensätze der Tabelle entspricht. ______________________________________________________________________________________________________ Oracle Trace Seite 40 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Für folgende Darstellung liegt ein sehr guter CLUSTERING_INDEX Wert vor, denn die Datensätze liegen fast alle in der gleichen Reihenfolge, wie sie im Index enthalten sind. Für das nachfolgende Beispiel müssen für den Index Range Scan die Datenblöcke zum Index quer durch die Tabelle ermittelt werden und fordert reichlich Memoryoperationen oder gar I/O. ______________________________________________________________________________________________________ Oracle Trace Seite 41 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] Meist ist das sortierte Speichern durch reorganisieren der Tabelle nicht möglich und der Zustand kann nicht geändert werden. 3.2.2. Index Selektivität Liegt ein Datenmodell vor, für das möglicherweise die Attribute der einzelnen Indizes in der falschen Reihenfolge eingesetzt wurden und das selektivste Attribut ist nicht an Position 1 eingetragen, fallen für die Bearbeitung eines Statements übermäßig viele CONSITENT GETS an. Wenn es fachlich möglich ist, sollte das selektivste Attribut eines Indizes an erster Position eingetragen sein. Welche Auswirkung sich ergeben für einen Verstoß dieser Anforderung, zeigt folgendes Beispiel. ______________________________________________________________________________________________________ Oracle Trace Seite 42 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Betrachten wir folgendes SELECT Statement: SELECT FROM WHERE AND AND * VERTRIEBSREGIONEN ADR_ID = 142 VERTRIEB_REGION_ID BETWEEN 110 AND 190 VERTRIEB_MITARBEITER_ID = 1 In der Tabelle VERTRIEBSREGIONEN sind ca. 760.000 Sätze gespeichert und es liegt ein Primary Key Index vor wie folgt: SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION FROM user_ind_columns WHERE table_name = ´VERTRIEBSREGIONEN´; INDEX_NAME ---------VTR_PK VTR_PK COLUMN_NAME -----------------VERTRIEB_REGION_ID ADR_ID COLUMN_POSITION --------------1 2 Die Selektivität (DISTINCT_KEYS, also Anzahl Datensätze pro Index-Key) der einzelnen Indexattribute erhält man: SELECT COUNT( DISTINCT( vertrieb_region_ID )) "DISTINCT_KEYS" FROM vertriebsregionen; DISTINCT_KEYS ------------4 SELECT COUNT( DISTINCT( adr_ID )) "DISTINCT_KEYS" FROM vertriebsregionen; DISTINCT_KEYS ------------127030 D.h., auf eine VERTRIEB_REGION_ID entfallen ca. 190.000 Datensätze (gesamte Anzahl Datensätze / DISTINCT_KEYS), wogegen ca. sechs der 760.000 Sätzen umfassenenden Tabelle einer ADR_ID zugeordnet sind. Der Wert DISTINCT_KEYS für SELECT DISTINCT_KEYS, NUM_ROWS FROM SYS.ALL_INDEXES WHERE index_name = 'VTR_PK' AND table_name = 'VERTRIEBSREGIONEN'; ______________________________________________________________________________________________________ Oracle Trace Seite 43 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] liefert die Selektivität für alle Attribute des Index, also VERTRIEBS_REGION_ID und ADR_ID in Kombination. Diese Angabe ist abhängig vom Änderungsverhalten der Tabelle seit dem letzten ANALYSE. Nun führen wir das SELECT Statement aus und erkennen am EXPLAIN PLAN, daß der Primary Key Index VTR_PK von dem Oracle Optimizer nicht genutzt wird. Explain Plan -------------------------------1.0 SELECT STATEMENT Mode=CHOOSE Cost (max) = 814 2.1 TABLE ACCESS FULL VERTRIEBSREGIONEN Die Ausführungszeit beträgt 3,120 Sekunden. Der FULL TABLE ACCESS wird vom Oracle Optimizer gewählt, weil sich für das erste IndexAttribut VERTRIEB_REGION_ID eine nicht abschätzbare Anzahl Indexsätze der Bereichsabfrage ergeben. Nämlich ca. 190.000 Index Einträge für jede VERTRIEBS_REGION_ID. Da maximal sechs ID's möglich sind ist ein FULL TABLE ACCESS keine schlechte Wahl. Ersetzt man VERTRIEB_REGION_ID BETWEEN 110 AND 190 gegen VERTRIEB_REGION_ID = 190 zeigt der Ausführungsplan sich wie folgt: Explain Plan -------------------------------1.0 SELECT STATEMENT Mode=CHOOSE Cost (max) = 3 2.1 TABLE ACCESS BY ROWID VERTRIEBSREGIONEN 3.1 INDEX RANGE SCAN VTR_PK Oracle signalisiert mit COST=3 einen guten Ausführungspfad. Ausführungszeit liegt jedoch nicht weit unter 3 Sekunden. Die Dieser Ausführungspfad wurde vom Oracle Optimizer gewählt, weil das erste Attribut des Index auf Gleichheit abgefragt wurde und somit die Anzahl der zu lesenden Index Einträge eine begrenzte Menge darstellt. Da diese "begrenzte" Anzahl jedoch ca. 190.000 Indexsätze umfaßt, also 1/6 des gesamten Indexbestandes, ist dieser Zugriffspfad ohne Vorteil. ______________________________________________________________________________________________________ Oracle Trace Seite 44 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Diese Betrachtung dient ausschließlich der Anschauung und zeigt deutlich, daß ein Indexzugriff nicht immer Geschwindigkeitsvorteile zur Folge haben muß. Dies wird noch einmal mit Einsatz eines Hint für die Abfrage deutlich. SELECT /*+ RULE */ * FROM VERTRIEBSREGIONEN WHERE ADR_ID = 142 AND VERTRIEB_REGION_ID BETWEEN 110 AND 190 AND VERTRIEB_MITARBEITER_ID = 1 Für den erzwungenen RULE Zugriff zeigt der folgende Ausführungsplan, daß der Index genutzt wird. Explain Plan -------------------------------1.0 SELECT STATEMENT Mode=HINT: RULE Cost (max) = 0 2.1 TABLE ACCESS BY ROWID VERTRIEBSREGIONEN 3.1 INDEX RANGE SCAN VTR_PK Aber auch hier liegt die Ausführungszeit bei 3 Sekunden. Warum aber nutzt der Optimizer im CHOOSE Modus den Index VTR_PK nicht? Betrachten wir hierzu noch einmal die zu Beginn ermittelten Distinct Keys der einzelnen Index-Attribute. INDEX-ATTRIBUT ---------------------VERTRIEB_REGION_ID ADR_ID DISTINCT_KEYS ------------4 127030 SAETZE pro DISTINCT_KEY ca. --------------------------190000 6 Da ein Index immer sortiert vorliegt (aufsteigend in der Reihenfolge der IndexAttribute), müssen wenigstens 190.000 aufeinander folgende Indexsätze gelesen werden. Aus dieser Ergebnismenge werden die Sätze mit übereinstimmender ADR_ID ausgewählt, um schließlich mit der ROWID den eigentlichen Datensatz aus der Tabelle zu selktieren. Da für einen Index das erste Attribut (hier die VERTRIEB_REGION_ID) mit dem nächsten (der ADR_ID) in direkter Verbindung steht, kann durch Vertauschen der Attribute in ihrer Reihenfolge (COLUMN_POSITION) die Anzahl der IndexSätze, die gelesen werden müssen, drastisch reduziert werden. Mit diesem Ziel wurde folgender Index angelegt. ______________________________________________________________________________________________________ Oracle Trace Seite 45 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] CREATE INDEX VTR_IDX1 ON VERTREIBSREGIONEN( ADR_ID, VERTRIEB_REGION_ID ); SELECT INDEX_NAME, COLUMN_NAME, COLUMN_POSITION FROM user_ind_columns WHERE index_name = ´VTR_IDX1´; INDEX_NAME ---------VTR_IDX1 VTR_IDX1 COLUMN_NAME -----------------ADR_ID VETRIEB_REGION_ID COLUMN_POSITION --------------1 2 Die Überprüfung des Index zeigt, dass die Positionen der Attribute für den neuen Index VTR_IDX1 gegenüber dem VTR_PK Index vertauscht sind. Für die wiederholte Ausführung des anfänglichen Statement SELECT * FROM VERTRIEBSREGIONEN WHERE ADR_ID = 142 AND VERTRIEB_REGION_ID BETWEEN 110 AND 190 AND VERTRIEB_MITARBEITER_ID = 1 zeigt sich der Explain Plan wie folgt. Explain Plan ----------------------------------------------------------1.0 SELECT STATEMENT Mode=CHOOSE Cost (max) = 3 2.1 TABLE ACCESS BY ROWID VERTRIEBSREGIONEN 3.1 INDEX RANGE SCAN VTR_IDX1 Der Indexzugriff erfolgt und die Laufzeit sank auf 0,038 Sekunden. Veränderungen am Statement, mit denen eine Reduzierung der Kosten (COST) verbunden war oder der Indexeinsatz im RULE Mode trugen nicht zur Laufzeitverbesserung bei. Erst der Index, für den das erste Attribut eine sehr hohe Selektivität aufwies konnte die Laufzeit von ca. 3 Sekunden auf 0,038 Sekunden verbessert werden. ______________________________________________________________________________________________________ Oracle Trace Seite 46 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] 3.3. Verschiedene Indextypen 3.3.1. B*tree Index Das B*tree (Binärbaum) Verfahren ist eine sehr verbreitete Art und Weise, Daten eines Index zu verwalten. Innerhalb eines B*tree Index liegen die Informationen sortiert vor, in Form eines Binärbaums. Somit muß für eine SELECT Anforderung nicht der gesamte Index gelesen werden, um sicher zu stellen, daß kein Eintrag übersehen wurde, sondern nur bis zu dem Punkt, an dem sich der Suchbegriff ändert. Hier ein Beispiel, in dem vorausgestzt wird, daß für das Attribut „name“ ein Index vorhanden ist: SELECT * FROM adressen WHERE name = ‘Maier‘; D.h., alle ROWIDs zu ‚Maier‘ sind gefunden, wenn z.B. ‚Meier‘ im Index gefunden wurde. Die ist deutlich zu erkennen in folgendem EXPLAIN PLAN. Zwei Sätze für einen Index Zugriff. Rows ------0 1784 584 1784 296 1784 1 2 1493660 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: CHOOSE HASH JOIN TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_PLZ' HASH JOIN TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ORT' HASH JOIN TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_NAME' INDEX GOAL: ANALYZED (RANGE SCAN) OF 'ADR_NAME_IDX' (NON-UNIQUE) TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ADRESSEN' Für den Index ADR_NAME_IDX werden zwei Sätze gelesen und für die Tabelle ADR_NAME nur einer. Es gilt die Attributreihenfolge zu beachten, wie sie bei der Anlage des Index bestimmt wurde. SQL > SELECT TABLE_NAME, COLUMN_NAME, COLUMN_POSITION FROM all_ind_columns WHERE index_name = 'ADRESSEN_IDX01'; TABLE_NAME -----------------------------ADRESSEN ADRESSEN COLUMN_NAME COLUMN_POSITION -------------- --------------PLZ 1 VORWAHL 2 ______________________________________________________________________________________________________ Oracle Trace Seite 47 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] PLZ wird als erstes Attribut für diesen Index ausgewiesen und VORWAHL als zweites. In einer WHERE Klausel muß mindestens das erste Attribut abgefragt werden. Hier sind <> oder NULL Abfragen falsch. Nachfolgender EXPLAIN PLAN zeigt, daß der Index benutzt wird. SQL > SELECT plz FROM adressen WHERE plz = 7774; Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Cost=7 Optimizer=CHOOSE 1 0 SORT (AGGREGATE) 2 1 INDEX (FULL SCAN) OF 'ADRESSEN_IDX01' Wogegen folgendes Statement zu einem FULL TABLE ACCESS oder einem INDEX FAST FULL SCAN führt. SQL > SELECT plz FROM adressen WHERE vorwahl = 7774; Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Cost=1053 Optimizer=CHOOSE 1 0 INDEX (FAST FULL SCAN) OF 'ADRESSEN_IDX01' Für einen Tabellen Zugriff über einen B*Tree Index werden 2-4 Datenblöcke gelesen. Drei für den Index (ein Header-, ein Branch- und ein Leaf Block, in dem die RowID zu dem Datensatz der Tabelle enthalten ist) und einer für die Tabelle. Folgendes Beispiel zeigt diesen Zusammenhang. Mit dem ersten SELECT wird erst einmal die RowID zu einem Datensatz der Tabelle ADR_ADRESSEN ermittelt. Aus der TKProf Ausgabe für das SELECT mit der Einschränkung der zuvor ermittelten RowID ist ersichtlich, daß genau ein CONSISTENT GETS (query) für die Bildung des Ergebnisses benötigt wird. Im Gegensatz dazu wird der Index Zugriff mit vier CONSISTENT GETS für den Indexzugriff ausgewiesen. Die drei „disk“ Zugriffe spiegeln die Indexblöcke wieder, denn der Datenblock der Tabelle wurde zuvor in der SGA gespeichert. SQL > SELECT rowid FROM adr_adressen WHERE id =1; ROWID -----------------AAAA6AAAFAAAFYMAAA ______________________________________________________________________________________________________ Oracle Trace Seite 48 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] 1 row selected. SQL > ****************************************************************************** select * from adr_anrede where rowid = 'AAAA6AAAFAAAFYMAAA' call count ------- -----Parse 1 Execute 1 Fetch 2 ------- -----total 4 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.01 0.00 0 0 0 0.00 0.00 0 0 0 0.00 0.00 0 1 0 -------- ---------- ---------- ---------- ---------0.01 0.00 0 1 0 rows ------0 0 1 ------1 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) Rows ------0 1 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: CHOOSE TABLE ACCESS GOAL: ANALYZED (BY USER ROWID) OF 'ADR_ANREDE' ****************************************************************************** select * from adr_adressen where id = 1 call count ------- -----Parse 1 Execute 1 Fetch 2 ------- -----total 4 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 0.00 0.00 0 0 0 0.00 0.11 3 4 0 -------- ---------- ---------- ---------- ---------0.00 0.11 3 4 0 rows -----0 0 1 -----1 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) Rows ------0 1 2 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: CHOOSE TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_ADRESSEN' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_ADRESSEN_ID_IDX' (UNIQUE) ****************************************************************************** Nur mit einem ROWNUM=1 kann ein SCAN vermieden werden. ****************************************************************************** SELECT FROM WHERE AND /*+ RULE */ * adr_plz id=10 rownum =1 call count ------- ------ cpu elapsed disk query current -------- ---------- ---------- ---------- ---------- rows ------- ______________________________________________________________________________________________________ Oracle Trace Seite 49 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] Parse 1 Execute 1 Fetch 1 ------- -----total 3 0.00 0.00 0 0 0 0.00 0.00 0 0 0 0.00 0.00 0 3 0 -------- ---------- ---------- ---------- ---------0.00 0.00 0 3 0 0 0 1 ------1 Misses in library cache during parse: 1 Optimizer goal: RULE Parsing user id: 21 (LAMBERTZ) Rows ------0 1 1 1 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: HINT: RULE COUNT (STOPKEY) TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_PLZ' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_PLZ_ID_IDX' (UNIQUE) ****************************************************************************** Die Anzahl der Datenblöcke, die mindestens für einen Index gelesen werden müssen, ist abhängig vom B*Level. Dieser Level spiegelt die Ausprägungshöhe eines Index wieder. Der B*Level 0, also die Leaf-Blocks, ist immer vorhanden! Mit dem folgenden Select Statement werden unterschiedliche Indizes und deren B*Level ausgegeben. SQL > SELECT 2 INDEX_NAME, 3 TABLE_NAME, 4 BLEVEL 5 FROM SYS.ALL_INDEXES 6 WHERE INDEX_NAME IN( 'ADR_ANREDE_ID_IDX', 'ADR_PLZ_ID_IDX', 'ADR_ADRESSEN_ID_IDX' ); INDEX_NAME -----------------------------ADR_ANREDE_ID_IDX ADR_PLZ_ID_IDX TABLE_NAME BLEVEL ------------------------------ ---------ADR_ANREDE 0 ADR_PLZ 1 ______________________________________________________________________________________________________ Oracle Trace Seite 50 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] ADR_ADRESSEN_ID_IDX ADR_ADRESSEN 2 Die TKProf Ausgabe zeigt, daß mit steigender Anzahl für den B*Level, die Anzahl Indexblöcke steigt, die mindestens verarbeitet werden. SELECT /*+ FIRST_ROWS */ * FROM adr_anrede where id=1 call count ------- -----Parse 1 Execute 1 Fetch 1 ------- -----total 3 cpu elapsed disk query current ------ ---------- ---------- ---------- ---------0.00 0.01 0 0 0 0.00 0.00 0 0 0 0.00 0.00 0 2 0 ------ ---------- ---------- ---------- ---------0.00 0.01 0 2 0 rows -------0 0 1 -------1 Es wurden zwei Datenblöcke als Query ausgewiesen. Einer für den Index und einer für die Tabelle. Im folgenden Select werden in Summe drei Query Blöcke ausgewiesen, entsprechend der höheren Anzahl B*Level. SELECT FROM WHERE AND /*+ FIRST_ROWS */ * adr_plz id=10 rownum =1 call count ------- -----Parse 1 Execute 1 Fetch 1 ------- -----total 3 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 0.00 0.00 0 0 0 0.00 0.00 0 3 0 -------- ---------- ---------- ---------- ---------0.00 0.00 0 3 0 rows ------0 0 1 ------1 SELECT /*+ FIRST_ROWS */ * from adr_adressen where id = 4711 call count ------- -----Parse 1 Execute 1 Fetch 1 ------- -----total 3 cpu elapsed disk query current -------- ---------- -------- ---------- ---------0.02 0.01 0 0 0 0.00 0.01 0 0 0 0.01 0.14 4 4 0 -------- ---------- -------- ---------- ---------0.03 0.16 4 4 0 rows -------0 0 1 -------1 ______________________________________________________________________________________________________ Oracle Trace Seite 51 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 3.3.2. Bitmap Index Ein Bitmap Index ist außerodentlich gut geeignet für Attribute mit geringer Selektivität. Und im Gegensatz zu einem B*Tree Index muß immer nur ein Indexblock gelesen werden. Durch die komprimierte Form sind in einem Bitmap Indexblock wesentlich mehr Referenzen zu Datensätzen einer Tabelle enthalten als in einem B*Tree Index, was jedoch für sehr dynamische Tabellen zu ungewünschten Locking Situationen führen kann. Für einen Bitmap werden für jede mögliche Ausprägung eines Attributs eine eingene Spalte im Index genutzt und der logische Zustand eingetragen Aus dem ursprünglichen Attribut einer Tabelle Anrede Herr Frau Firma Herr 1 0 0 Frau 0 1 0 Firma 0 0 1 Herr 1 0 0 Herr 1 0 0 Kombiniert mit weiteren Attributen der Tabelle entsteht ein Index mit sehr hoher Selektivität. 3.3.3. Hash Index 3.4. Wann Oracle einen Index im CHOOSE Mode nicht nutzt Häufig ist zu erkennen, daß ORACLE einen Index nicht nutzt, obwohl es augenscheinlich sinnvoll erscheint. Im RULE Mode hingegen benutzt Oracle grundsätzlich einen Index, wenn einer existiert. 3.4.1. Zu große Anzahl von gelöscht markierten Indexeinträgen Wird ein Datensatz in einer Tabelle gelöscht, erfolgt im Index keine Löschung des referenzierten Eintrags, sondern er wird als gelöscht markiert. ______________________________________________________________________________________________________ Oracle Trace Seite 52 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Für Oracle ist diese Methode wesentlich schneller, den B*Tree auf dem aktuellen Stand zu halten, als durch eine Reorganisation des Index. 3.4.2. Geringes Datenvolumen der Tabelle Für Tabellen mit geringem Datenvolumen können alle Datensätze mit einer einzigen Leseoperation in den Speicher gelesen werden. In Tabellenstatistiken ist die Anzahl gesamter Datenblöcke beschrieben, welche Tabelle benötigt werden. Ist die Anzahl Datenblöcke kleiner oder gleich der mit DB_FILE_MULTIBLOCK_READ_COUNT*DB_BLOCK_SIZE bestimmten Anzahl, wird die Tabelle im FULL TABLE ACCESS gelesen und nicht mit einem Index. Zu beachten ist die HIGH WATER MARK einer Tabelle. Es kann vorkommen, das laut Statistik eine einziger Zugriff ausreicht die gesamte Tabelle in den Speicher zu laden. Eine Tabelle wird im FULL TABLE ACCESS jedoch immer bis zur HIGH WATER MARK gelesen und im schlechtesten Fall liegt die Anzahl Blöcke, die tatsächlich gelesen werden müssen, um ein vielfaches höher als die Statistiken es ausgewiesen haben. 3.4.3. Einschränkung mit != oder <> Einschränkungen in der WHERE Klausel mit != oder <> führen unweigerlich zum FULL TABLE ACCESS. Zu ungenau ist die Einschränkung auf die Ergebnismenge. Selbst ein Hint bewirkt selten das Gegenteil. 3.4.4. Unbekannte Selektivität durch Bereichsabfragen Für den Fall, daß in der WHERE Klausel eines Statement ein Bereich für ein Attribut angegeben wurde („>“, „<“ oder BETWEEN) erfolgt häufig ein FULL TABLE ACCESS. Ursache liegt darin, daß ORACLE die Größt- und Kleistwerte aus den Statistiken kennt und daraus ermittelt, wie hoch die Wahrscheinlichkeit ist, daß für die Einschränkung nicht mehr als ca. 20% der Daten selektiert werden müssen und für den Fall erfolgt ein FULL TABLE ACCESS Zugriff (ich gebe zu, daß die Angabe von 20% nicht belegt ist, jedoch liegen verschiedenste Vergleiche vor, die aussagen, daß mehr als 20% Daten einer Tabelle schneller per FULL TABLE ACCESS ermittelt werden können, als über einen Index). Der Einsatz von Bindevariablen zeigt sich an dieser Stelle problematisch. ______________________________________________________________________________________________________ Oracle Trace Seite 53 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 3.4.5. Unbekannte Selektivität durch Einsatz von Bindevariablen Für folgendes Beispiel wird Oracle den Indexzugriff bevorzugen, wenn ein entsprechender Index vorhanden ist. SELECT * FROM emp WHERE empno = :a Im Gegenteil dazu das folgende Statement. SELECT * FROM emp WHERE empno > :a Oracle führt in den Statistiken den kleinsten und größten Wert des indizierten Attributes einer Tabelle. Durch den Einsatz der Bindevariablen kann nicht mehr bestimmt werden wie viele Daten womöglich gelesen werden müssen und der FULL TABLE ACCESS erhält den Vorrang vor einem Index Zugriff. D.h., wenn für die Tabelle „emp“ zehn Datensätze gespeichert sind und das Attribut „empno“ die Werte 1 bis 10 trägt, ist es ein Unterschied, ob die Bindevariable den Wert 2 oder 9 beinhaltet. Für den ersten Fall müssen sieben Datensätze gelesen werden wogegen im zweiten Fall gerade mal ein Datensatz gelesen werden müßte. 3.4.6. Unterbrechung der Reihenfolge von Indexattributen Für das Anlegen eines Index spielt die Angabe der Reihenfolge der Attribute zu diesem Index eine große Rolle. Die Tabelle ADR_ADRESSEN erhält folgenden Index: CREATE INDEX adr_adressen_idx01 ON adr_adressen( id, vorname ) / Auch wenn in der WHERE Klausel des folgenden Select Statements nur eines der drei Index Attribute abgefragt wird, erfolgt der Zugriff über den Index. ______________________________________________________________________________________________________ Oracle Trace Seite 54 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] SELECT * FROM adr_adressen WHERE id =24244; Execution Plan ----------------------------------------------------------OPIMIZER=CHOOSE cost= 3 1.1 TABLE ACCESS BY INDEX ROWID LAMBERTZ.ADR_ADRESSEN 2.1 INDEX UNIQUE SCAN LAMBERTZ.ADR_ADRESSEN_ID_IDX Ursache ist, daß es sich um das erste Attribut des Index handelt. Anders für folgendes Statement: SELECT * FROM adr_adressen WHERE vorname = 'Heinz'; Execution Plan ----------------------------------------------------------OPIMIZER=CHOOSE cost= 3016 1.1 TABLE ACCESS FULL LAMBERTZ.ADR_ADRESSEN Der Zugriff erfolgt im FULL TABLE ACCESS, weil das erste Attribut des Index nicht abgefragt wurde. Wird das erste Attribut temporär gefüllt mit einer Angabe, die keine wirkliche Einschränkung darstellt, läßt sich Oracle oft zu einem Index Zugriff „hinreißen“. SELECT FROM WHERE AND * adr_adressen vorname = 'Heinz' id > 0; Von dieser Lösung ist abzuraten, denn eine Laufzeitverbesserung wird sich nicht einstellen. Grundsätzlich werden nur die Attribute eines Index genutzt, für die keine Unterbrechung in der Reihenfolge vorliegt, mit der sie angelegt wurden. Immer wenn die Reihenfolge unterbrochen wird, erfolgt der Indexzugriff mit den Attributen bis zu der Unterbrechung. Hierzu ein Beispiel: CREATE INDEX Tabelle_idx01 ON Tabelle( attr1, attr2, attr3, attr4 / SELECT WHERE AND AND ) * FROM Tabelle attr1 = 1234 attr2 = 4711 attr4 = 4321; Der Index Tabelle_idx01 für die Tabelle TABELLE wird nur mit den Attributen „attr1“ und „attr2“ angesprochen. Dies hat meistens Nachteile für die Laufzeit ______________________________________________________________________________________________________ Oracle Trace Seite 55 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] zur Folge gegenüber einem Zugriff über alle Attribute des Indizes. Nicht aber, wenn die ersten Attribute sehr eindeutig sind. 3.4.7. Füllgrad des Index-Key Eine Tabelle mit zwei Indizes liegt vor. CREATE TABLE adr_idx_tst ( anrede vorname name strasse plz ort telefon vorwahl rufnummer ) / VARCHAR2(10), VARCHAR2(25), VARCHAR2(90), VARCHAR2(60), NUMBER(5), VARCHAR2(60), VARCHAR2(15), NUMBER(10), VARCHAR2(30) CREATE INDEX adr_idx_tst_idx01 ON adr_idx_tst ( name, vorname ) / CREATE INDEX adr_idx_tst_idx02 ON adr_idx_tst ( name, vorname, strasse, plz ) / Nun erfolgt ein SELECT in dem drei Tabellenattribute auf Gleichheit abgefragt werden. Die ersten beiden Attribute sind in beiden Indizes vorhanden und zwei weitere nur in einem Index. Der SELECT erfolgt ohne Statistiken, also im RULE Mode (COST wird mit 0 ausgewiesen). SELECT FROM WHERE AND AND * adr_idx_tst name = 'Asche' vorname = 'Jurgen' strasse = 'Maximilianstr. 2' Execution Plan -------------------------------------------------------------OPIMIZER=CHOOSE cost= 0 1.1 TABLE ACCESS BY INDEX ROWID LAMBERTZ.ADR_IDX_TST 2.1 INDEX RANGE SCAN LAMBERTZ.ADR_IDX_TST_IDX01 ______________________________________________________________________________________________________ Oracle Trace Seite 56 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Im RULE Mode entscheidet sich der Oracle Optimizer für den Index „ADR_IDX_TST_IDX01“, weil alle Attribute des Index in derWHERE Klausel abgefragt werden. Mit dem Index können aber nur die Sätze ermittelt werden, welche der Gleichheit von „name“ und „vorname“ entsprechen. Das sind jedoch mehr Datensätze, als mit dem dritten Attribut aus der WHERE Klausel gewünscht sind. Oracle löst das, indem alle Datensätze aus dem Index ermittelt werden, welche der Bedingung für die Indexattribute entsprechen (drei Indexsätze) und selektiert jedoch nur einen Datensatz aus der Tabelle aufgrund der kompletten WHERE Bedingung. ****************************************************************************** SELECT * FROM adr_idx_tst WHERE name = 'Barth' AND vorname = 'Dr. Christian' AND strasse = 'Im Grund 16' call count ------- -----Parse 1 Execute 1 Fetch 2 ------- -----total 4 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 0.00 0.00 0 0 0 0.00 0.00 0 4 0 -------- ---------- ---------- ---------- ---------0.00 0.00 0 4 0 rows ---------0 0 1 ---------1 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) Rows ------0 1 3 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: CHOOSE TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_IDX_TST' INDEX GOAL: ANALYZED (RANGE SCAN) OF 'ADR_IDX_TST_IDX01' (NON-UNIQUE) ****************************************************************************** D.h., wenn mehere Indizes vom gleichen Typ (UNIQUE oder nicht) vorliegen, wird im RULE Mode der genutzt, für den entweder alle oder die meisten Attribute in der WHERE Klausel erscheinen. Für den CHOOSE MODE gilt das nicht grundsätzlich. Auch wenn alle Attribute aus dem Index „adr_idx_tst_idx02“ Einsatz in der WHERE Klausel finden, erfolgt der Zugriff über den Index „adr_idx_tst_idx01“. Das liegt daran, daß für diesen Index mehr als 1/3 Drittel der Tabellenattribute indiziert sind. SELECT * FROM adr_idx_tst WHERE name = 'Asche' ______________________________________________________________________________________________________ Oracle Trace Seite 57 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] AND vorname = 'Jurgen' AND strasse = 'Maximilianstr. 2' AND PLZ = 78267; Execution Plan ----------------------------------------------------------OPIMIZER=CHOOSE cost= 2 1.1 TABLE ACCESS BY INDEX ROWID LAMBERTZ.ADR_IDX_TST 2.1 INDEX RANGE SCAN LAMBERTZ.ADR_IDX_TST_IDX01 3.4.8. Fehlende Statistiken Ein Grund, daß im CHOOSE Mode ein vorhandener Index nicht zum Einsatz gelangt, können fehlende Statistiken auf nicht allen Tabellen des Statements sein. Voraussetzung ist, das für wenigstens eine Tabelle oder Index Statistikwerte vorliegen (Statistiken weisen die Anzahl Rows einer Tabelle aus, geben Aussage, wie selektiv ein Index ist, usw.). Ohne jegliche Statistiken erfolt ein RULE Zugriff und der arbeitet vorrangig mit Indizes. Mit hilfe von Statistiken setzt Oracle das Statement in eine Form um, welche den RULE Regeln entspricht! Erst dann wird es ausgeführt. 3.4.9. Veraltete Statistiken Für Tabellen, in denen im gleichen Verhältnis Datensätze gelöscht, sowie eingefügt werden, veralten Statistiken kaum. Auch für Tabellen, an denen kaum Änderungen erfolgen, müssen die Statistiken recht selten neu erzeugt werden. Jedoch sind für Tabellen mit extremen Wachstum (z.B. Data Warehouse) die Statistiken schnell veraltet. Dies muß nicht zwingend zu einem Laufzeitproblem führen. Tabellen, die einer extremen Schwankung im Datenvolumen unterliegen, können nicht beliebig mit neuen Statistiken versehen werden. Erst recht nicht, wenn das gesamte Datenvolumen recht groß ist, weil die Erstellung zeitkritisch ist. Für den Fall muß überlegt werden, ob der Zugriff auf diese Tabellen nicht durchweg regelbasiert erfolgen soll. Aber dann müssen die Statements unbedingt den Regeln entsprechen!! 3.4.10. Abfrage mit IS NULL ______________________________________________________________________________________________________ Oracle Trace Seite 58 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Außer für einen Bitmap Index werden NULL Werte für Attribute einer Tabelle nicht indiziert. D.h., wenn ein B*tree oder Hash Index zum Einsatz gelangen, führt die Abfrage IS NULL unweigerlich zum FULL TABLE ACCESS. 3.4.11. Abfrage IS NOT NULL IS NOT NULL ist keine wirkliche Einschränkung in der WHERE Klausel. Auch wenn für das Attribut, welches auf IS NOT NULL abgefragt wird ein Index vorliegt der genutzt werden kann, heißt IS NOT NULL nur, daß alle Indexeinträge der Bedingung entsprechen. Dann ist ein FULL TABLE ACCESS statistisch einfach erfolgreicher. Das weiß auch Oracle! 3.4.12. Mehrere Indizes Sind für eine Tabelle mehere Indizes vorhanden, die sich laut Definition unterscheiden, gilt folgende Hirarchie Unique Key Einfacher Index Auch wenn der Unique Key Index aus nur einem Attribut besteht und der einfache Index aus meheren Attributen, welche alle in der WHERE Klausel vorhanden sind. CREATE INDEX IDX_01 ON test_Tabelle( ID1, ID2, CD ); CREATE UNIQUE INDEX IDX_UK ON test_Tabelle( CD ); SELECT FROM WHERE AND AND Bezeichnung, Kennung, aend_dat, aend_user Test_Tabelle id1 = 50206 id2 = 50156 cd = 'ABCDE' Execution Plan -----------------------------------------------1.1 TABLE ACCESS BY INDEX ROWID TEST_TABELLE 2.1 INDEX UNIQUE SCAN IDX_UK Hinzu kommt, daß Oracle einen Index höher bewertet, für den alle Attribute in der WHERE Klausel enthalten sind. ______________________________________________________________________________________________________ Oracle Trace Seite 59 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 3.4.13. Ungünstiger Clustering_Index Liegt der Clustering Faktor für einen Index nahe an der Gesamtanzahl Datensätze für eine Datentabelle, nutzt Oracle nur selten den Index. 3.4.14. LIKE in der WHERE Klausel Folgende Tabellen werden eingesetzt, um den LIKE Parameter zu bewerten. SQL > DESC adr_adressen; Name Null? ------------------------------- -------ID ANREDE_ID VORNAME NAME_ID STRASSE PLZ_ID ORT_ID TELEFON VORWAHL_ID RUFNUMMER Type ---NUMBER(7) NUMBER(7) VARCHAR2(25) NUMBER(7) VARCHAR2(60) NUMBER(7) NUMBER(7) VARCHAR2(15) NUMBER(7) VARCHAR2(30) SQL > DESC adr_name; Name Null? ------------------------------- -------ID NAME Type ---NUMBER(7) VARCHAR2(90) Für die aufgelisteten Tabellen sind folgende Indizes vorhanden: SELECT table_name, index_name, column_name, column_position FROM user_ind_columns WHERE table_name IN( 'ADR_ADRESSEN', 'ADR_NAME' ); TABLE_NAME -------------ADR_ADRESSEN ADR_NAME ADR_NAME INDEX_NAME --------------------ADR_ADRESSEN_ID_IDX ADR_NAME_IDX ADR_NAME_ID_IDX COLUMN_NAME ------------ID NAME ID COLUMN_POSITION --------------1 1 1 ______________________________________________________________________________________________________ Oracle Trace Seite 60 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Für die erste Afrage erfolgt die Einschränkung mit ‘%Meyer%‘ SELECT * FROM adr_adressen, adr_name WHERE adr_name.name like '%Meyer%' AND adr_adressen.name_id = adr_name.id Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Cost=3447 Optimizer=CHOOSE 1 0 HASH JOIN 2 1 TABLE ACCESS (FULL) OF 'ADR_NAME' 3 1 TABLE ACCESS (FULL) OF 'ADR_ADRESSEN' Der Ausführungsplan zeigt einen FULL TABLE ACCESS auf die Tabelle ADR_ADRESSEN, weil durch den führenden und abschließenden Platzhalter ‘%‘ die Ergebnismenge beliebig groß werden kann. D.h., ein RANGE SCAN über den gesamten Index wäre die Folge. Anders dagegen, wenn die Einschränkung erfolgt ohne führenden Platzhalter. Für den Fall ist die Ergebnismenge kalkulierbar und der Index ein Vorteil. SELECT * FROM adr_adressen, adr_name WHERE adr_name.name like 'Meyer%' AND adr_adressen.name_id = adr_name.id Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Cost=3024 Optimizer=CHOOSE 1 0 HASH JOIN 2 1 TABLE ACCESS (BY INDEX ROWID) OF 'ADR_NAME' 3 2 INDEX (RANGE SCAN) OF 'ADR_NAME_IDX' 4 1 TABLE ACCESS (FULL) OF 'ADR_ADRESSEN' ______________________________________________________________________________________________________ Oracle Trace Seite 61 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 4. Erklärung eines Explain Plan Ein EXPLAIN PLAN ist die dokumentierte Vorgehensweise für die Bearbeitung eines Statements. Für die Erstellung eines EXPLAN PLAN muß das Statement nicht ausgeführt werden, sondern es reicht eine Anforderung an Oracle die Vorgehensweise in einer Tabelle PLAN_TABLE zu speichern. Für einen Trace gilt das nicht. Dort erfolgt vor der Ermittlung des EXPLAIN PLAN die eigentliche Ausführung des Statements um Angaben über die Anzahl der tatsächlich gelesenen Rows oder Laufzeiten der einzelnen Statements treffen zu können. Nur wenn ein EXPLAIN PLAN ausdrücklich mit dem SQL Kommando „EXPLAIN PLAN SET ..“ erstellt wird, kann der Name der Tabelle frei gewählt werden. Für das implizite Erstellen eines EXPLAIN PLAN, z.B. mit AUTOTRACE unter SQLPLUS oder der Übersetzung eines *trc Files mit dem Tool „tkprof..“, wird eine Tabelle von Oracle vorausgesetzt, die den Namen PLAN_TABLE trägt. Sie kann entweder als Synonym oder als Tabelle des Users vorliegen. In jedem Fall muß sie unter ORACLE 8.0x der nachfolgenden Form entsprechen: CREATE TABLE plan_table( statement_id varchar2(30), timestamp date, remarks varchar2(80), operation varchar2(30), options varchar2(30), object_node varchar2(128), object_owner varchar2(30), object_name varchar2(30), object_instance numeric, object_type varchar2(30), optimizer varchar2(255), search_columns number, id numeric, parent_id numeric, position numeric, cost numeric, cardinality numeric, bytes numeric, other_tag varchar2(255), partition_start varchar2(255), partition_stop varchar2(255), partition_id numeric, other long ) / grant select, insert, update, delete on plan_table to public; create public synonym plan_table for sys.plan_table; ______________________________________________________________________________________________________ Oracle Trace Seite 62 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Weicht die Struktur ab, wird an der sonst üblichen Stelle (AUTOTRACE oder Trace) eine Fehlermeldung erscheinen. Explizit erstellt man einen EXPLAIN PLAN mit einem SQL Kommando: EXPLAIN PLAN SET statement_id='LC' INTO my_plan_table for @&File_Name Dieser Aufruf führt dazu, daß ORACLE einen EXPLAIN PLAN erstellt und in der angegebenen Tabelle my_plan_table speichert. Die Statement_id ist nicht festgelegt. Hier lautet sie 'LC'. Die ID hat die Aufgabe, alle Einträge zu einem EXPLAIN PLAN eindeutig zu identifizieren. Zu beachten ist, wenn noch veraltete Werte in der Tabelle mit der gleichen ID enthalten sind, die Darstellung des EXPLAIN entwas bizarr wirken kann, weil die neuen Einträge nicht von den alten zu unterscheiden sind. @&File_Name ist eine Variable und gibt den Namen der Datei an, in der das Statement enthalten ist, für das ein EXPLAIN PLAN erstellt werden soll. Zu beachten ist, das in dieser Datei nichts außer dem einen Statement enthalten sein darf und dieses mit einem „;“ abgeschlossen sein muß! Für die Auswertung wird ein SELECT Statement eingesetzt, welches die PLAN_TABLE Ergebnisse aufbereitet darstellt. Generating Execution plan for 1 SELECT anr.ANREDE 2 , name.Name 3 , adr.VORNAME 4 , adr.STRASSE 5 , plz.plz 6 , ort.ort 7 , adr.TELEFON 8 , vhl.vorwahl 9 , adr.RUFNUMMER 10 FROM LAMBERTZ.ADR_ADRESSEN adr 11 , LAMBERTZ.ADR_anrede anr 12 , LAMBERTZ.ADR_name name 13 , LAMBERTZ.ADR_Ort ort 14 , LAMBERTZ.ADR_plz plz 15 , LAMBERTZ.ADR_Vorwahl vhl 16 WHERE anr.ID = adr.ANREDE_ID 17 AND vhl.ID = adr.VORWAHL_ID 18 AND name.ID = adr.NAME_ID 19 AND plz.ID = adr.PLZ_ID 20 AND ort.ID = adr.ORT_ID 21* AND name.name = 'Meier'; SELECT STATEMENT OPIMIZER=CHOOSE cost= 3028 Execution Plan Cost Distincts Bytes ------------------------------------------------------------ ---------- ---------- ------1.1 HASH JOIN 2.1 TABLE ACCESS FULL LAMBERTZ.ADR_VORWAHL 2.2 HASH JOIN 3.1 TABLE ACCESS FULL LAMBERTZ.ADR_PLZ 3.2 HASH JOIN 3028 1 3026 1 3024 572 164 349 164 213 108680 4264 57236 4264 29394 ______________________________________________________________________________________________________ Oracle Trace Seite 63 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 4.1 TABLE ACCESS FULL LAMBERTZ.ADR_ORT 4.2 HASH JOIN 5.1 TABLE ACCESS FULL LAMBERTZ.ADR_ANREDE 5.2 NESTED LOOPS 6.1 TABLE ACCESS BY INDEX ROWID LAMBERTZ.ADR_NAME 7.1 INDEX RANGE SCAN LAMBERTZ.ADR_NAME_IDX 6.2 TABLE ACCESS FULL LAMBERTZ.ADR_ADRESSEN 1 3022 1 3020 4 3 3016 82 260 4 260 1 1 1493217 3690 24180 40 21580 24 88099803 Diese Darstellung ist deshalb sehr gut, weil nicht nur der EXPLAIN PLAN zu erkennen ist, sondern auch die COST der einzelnen Operationen, die DISTINCTS und die geschätzten BYTES, die an die nächst höhere Operation geliefert werden. Nun, die erste Operation, die ORACLE durchführt, ist die mit der höchsten Nummer (hier die 7) mit kleinstem Dezimalanteil (7.1). Die treibende Tabelle ist streng genommen der Index ADR_NAME_IDX, jedoch gilt die Tabelle zu dem Index als treibend und nicht der Index selbst. Die Position der treibenden Tabelle befindet sich nicht zwingend am Ende des EXPLAIN PLAN. Je nach Komplexität kann sie in der Mitte oder gar zu Beginn des PLAN liegen. Die Angaben COST, DISTINCTS, BYTES basieren auf den Statistiken, die von ORACLE nach einem ANALYZE angelegt wurden. Liegen keine Statistiken auf allen beteiligten Tabellen des Statements vor und nur dann erfolgt der Zugriff im RULE Mode (auch wenn CHOOSE angezeigt wird). Wurde mit einem RULE Hint gearbeitet, werden diese Werte nicht angezeigt. Die Anzahl Byte für einen Datensatz ergeben sich aus: Bytes = AVG Row Len * Anzahl Rows. AVG Row Len = Bytes / Anzahl Rows (88099803 / 1493217 = 59). Auch wenn diese Zahlen nicht bis ins Letzte stimmig sind, ist es hilfreich für die Einschätzung der erforderlichen Ressourcen, die für eine Teiloperation benötigt werden. Der TABLE ACCESS FULL auf die Tabelle ADR_ADRESSEN ist demnach wesentlich kritischer als für die Tabelle ADR_ANREDE. Jetzt ist auch sofort klar, warum ein FULL TABLE ACCESS nicht immer kritisch ist!! Auch die COSTen summieren sich von der höchsten bis zur kleinsten Positionsnummer (von 3016 auf 3028). In dem Beispiel ist aufgrund der COST Angababen zu erkennen, daß der FULL TABLE ACCESS auf die Tabelle ADR_ADRESSEN die meisten Reccoursen benötigt. Alle Zahlenwerte einer Zeile gelten für die Aktion, in der sie zu finden sind. So liefert z.B. der Index an Position 7.1 einen Satz und folglich kann nur ein Satz aus der Tabelle zu Position 6.1 selektiert werden. Gesamt gesehen wurden zu diesem Zeitpunkt schon 2 Sätze gelesen. Auch wenn der INDEX hier im RANGE SCAN angegeben wurde, handelt es sich um einen UNIQUE INDEX, der nur nicht als solcher deklariert ist (siehe Kapitel INDEX RANGE SCAN). ______________________________________________________________________________________________________ Oracle Trace Seite 64 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Für den FULL TABLE ACCESS werden ca. 1493217 Datensätze gelesen. Position 6.1 und 6.2 sind Datenlieferant für den NESTED LOOP der Position 5.2. D.h., der NEESTED LOOP reduziert die Daten (1493217 +1) auf 260 Datensätze. Position 5.1 und 5.2 liefern zusammen 264 Datensätze, welche mit hilfe des HASH JOIN wieder auf 260 Datensätze reduziert werden, usw. Werden die Ergebnisse der Tabelle PLAN_TABLE nun grafisch dargestellt, sind dies Zusammenhänge sofort deutlich. HASH JOIN<--TABLE ACCESS ^ FULL | ADR_VORWAHL | | +-----------HASH JOIN<--TABLE ACCESS ^ FULL | ADR_PLZ | | +-----------HASH JOIN<--TABLE ACCESS ^ FULL | ADR_ORT | | +-----------HASH JOIN<--TABLE ACCESS ^ FULL | ADR_ANREDE | | +-----------NESTED LOOPS<--TABLE ACCESS<---INDEX ^ BY INDEX ROW RANGE SCAN | ADR_NAME ADR_NAME_IDX | | +--------------TABLE ACCESS FULL ADR_ADRESSEN 4.1. Operationen eines Explain Plan Für die Darstellung eines Explain Plan, existieren Begriffe, deren Bedeutung nun gezeigt werden soll. 4.1.1. TABLE ACCESS (FULL) Der Zugriff auf die Tabelle erfolgt indem alle Daten der Tabelle in den Buffer eingelesen werden um hieraus die Ergebnismenge zu bilden. Es werden alle Datenblöcke der Tabelle gelesen bis zur HIGH WATER MARK. ______________________________________________________________________________________________________ Oracle Trace Seite 65 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 4.1.2. TABLE ACCESS (CLUSTER) Einlesen der Daten aus einer Tabelle mit einem Cluster Key Index. 4.1.3. TABLE ACCESS (HASH) Ist für die Datenbank Instanz der initSID.ora Parameter HASH_JOIN_ENABLED=TRUE gesetzt, findet man in einem Explain Plan häufig einen TABLE ACCESS (HASH), wenn nicht dieser Zugriff durch einen Hint übersteuert wurde. Es handelt sich um einen HASH JOIN Index, der von Oracle zur Laufzeit in der SGA angelegt wird, für die Daten der Tabelle, welche einmalig im TABLE ACCESS (FULL) gelesen wurde. Angezeigt wird nur der TABLE ACCESS (HASH) Zu einem HASH JOIN gehört mindestens eine Tabelle, die gegen eine weitere Tabelle oder eine Zwischenmenge gelesen wird, z.B. aus einem NESTED LOOP. Sidn zwei Tabellen an dem HASH JOIN beteiligt, wird gewöhnlich die größere der beiden Tabellen mit einem HASH Index versehen. DB_BUFFER Daten einer Tabelle die im FULL TABLE ACCESS gelesen wurde, sind in der LRU Liste weit vorne zu finden, was heißt, daß diese Daten sehr schnell wieder aus dem Speicher entfernt werden, wenn es erforderlich werden sollte, der HASH INDEX jedoch nicht. SELECT anr.ANREDE , name.Name , adr.VORNAME , adr.STRASSE , plz.plz , ort.ort , adr.TELEFON , vhl.vorwahl , adr.RUFNUMMER FROM LAMBERTZ.ADR_ADRESSEN adr , LAMBERTZ.ADR_anrede anr , LAMBERTZ.ADR_name name , LAMBERTZ.ADR_Ort ort , LAMBERTZ.ADR_plz plz , LAMBERTZ.ADR_Vorwahl vhl WHERE anr.ID = adr.ANREDE_ID AND vhl.ID = adr.VORWAHL_ID AND name.ID = adr.NAME_ID AND plz.ID = adr.PLZ_ID AND ort.ID = adr.ORT_ID AND name.name = 'Meier' call count ------- -----Parse 1 Execute 1 Fetch 595 ------- -----total 597 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.01 0.02 0 0 0 0.00 0.00 0 0 0 4.34 12.42 12595 13187 20 -------- ---------- ---------- ---------- ---------4.35 12.44 12595 13187 20 rows ---------0 0 1784 ---------1784 Misses in library cache during parse: 1 ______________________________________________________________________________________________________ Oracle Trace Seite 66 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) Rows ------0 1784 624 1784 584 1784 296 1784 4 1784 2 2 1784 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: CHOOSE HASH JOIN TABLE ACCESS (FULL) OF 'ADR_VORWAHL' HASH JOIN TABLE ACCESS (FULL) OF 'ADR_PLZ' HASH JOIN TABLE ACCESS (FULL) OF 'ADR_ORT' HASH JOIN TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ANREDE' NESTED LOOPS TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_NAME' INDEX GOAL: ANALYZED (RANGE SCAN) OF 'ADR_NAME_IDX' (NON-UNIQUE) TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ADRESSEN' ****************************************************************************** Außer zwei Tabellen werden alle im HASH JOIN gelesen. Also einmal FULL TABLE ACCESS und organisieren der Daten mit einem HASH INDEX im Speicher. Gerade wenn Tabellen zu Beginn eines EXPLAIN PLAN zu finden sind, zeigt der HASH Zugriff Vorteile. Denn jeder Datensatz aus der tieferen Ebene muß mit dem aus der nächst höheren Ebene verglichen werden. Und wenn dieser zu vergleichende Datensatz mit hilfe eines Index ermittelt werden kann, sind hier Geschwindigkeitsvorteile zu erkennen. Aber nur, wenn die SGA ausreichend groß dimensioniert ist! Fazit ist, daß trotz des Mehraufwandes -Indexbildung zur Laufzeit- dieser Zugriff schneller ist als ein klassischer Index Zugriff, wie das nachfolgende Beispiel zeigt. Zu Beginn der Zugriff mit HASH_JOIN_ENABLED = FALSE. ****************************************************************************** SELECT anr.ANREDE , name.Name , adr.VORNAME , adr.STRASSE , plz.plz , ort.ort , adr.TELEFON , vhl.vorwahl , adr.RUFNUMMER FROM LAMBERTZ.ADR_ADRESSEN adr , LAMBERTZ.ADR_anrede anr , LAMBERTZ.ADR_name name , LAMBERTZ.ADR_Ort ort , LAMBERTZ.ADR_plz plz , LAMBERTZ.ADR_Vorwahl vhl WHERE anr.ID = adr.ANREDE_ID AND vhl.ID = adr.VORWAHL_ID AND name.ID = adr.NAME_ID AND plz.ID = adr.PLZ_ID AND ort.ID = adr.ORT_ID AND name.name = 'Meier' ______________________________________________________________________________________________________ Oracle Trace Seite 67 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] call count ------- -----Parse 1 Execute 1 Fetch 595 ------- -----total 597 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.07 0.14 3 297 3 0.00 0.00 0 0 0 4.12 13.12 12602 12600 20 -------- ---------- ---------- ---------- ---------4.19 13.26 12605 12897 23 rows ---------0 0 1784 ---------1784 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) Rows ------0 1784 1785 1784 1785 1784 1785 1784 1785 1784 2 2 1784 1784 4 1784 296 1784 584 1784 624 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: CHOOSE MERGE JOIN SORT (JOIN) MERGE JOIN SORT (JOIN) MERGE JOIN SORT (JOIN) MERGE JOIN SORT (JOIN) NESTED LOOPS TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_NAME' INDEX GOAL: ANALYZED (RANGE SCAN) OF 'ADR_NAME_IDX' (NON-UNIQUE) TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ADRESSEN' SORT (JOIN) TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ANREDE' SORT (JOIN) TABLE ACCESS (FULL) OF 'ADR_ORT' SORT (JOIN) TABLE ACCESS (FULL) OF 'ADR_PLZ' SORT (JOIN) TABLE ACCESS (FULL) OF 'ADR_VORWAHL' ****************************************************************************** Somit ist der Indexzugriff an dieser Stelle aufwendiger, als im DB_BUFFER zur Laufzeit einen Index über die komplette Tabelle zu erzeugen. Für einen TABLE ACCESS (HASH) sollten einige Parameter in der initSID.ora Datei gesetzt sein: • HASH_AREA_SIZE = 2 * SORT_AREA_SIZE • HASH_MULTIBLOCK_IO_COUNT Der DB_FILE_MULTIBLOCK_READ_COUNT Parameter spielt an der Stelle keine Rolle. ______________________________________________________________________________________________________ Oracle Trace Seite 68 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Nun also der Zugriff mit einigen (HASH_JOIN_ENABLED = TRUE) TABLE ACCESS (HASH) ****************************************************************************** SELECT anr.ANREDE , name.Name , adr.VORNAME , adr.STRASSE , plz.plz , ort.ort , adr.TELEFON , vhl.vorwahl , adr.RUFNUMMER FROM LAMBERTZ.ADR_ADRESSEN adr , LAMBERTZ.ADR_anrede anr , LAMBERTZ.ADR_name name , LAMBERTZ.ADR_Ort ort , LAMBERTZ.ADR_plz plz , LAMBERTZ.ADR_Vorwahl vhl WHERE anr.ID = adr.ANREDE_ID AND vhl.ID = adr.VORWAHL_ID AND name.ID = adr.NAME_ID AND plz.ID = adr.PLZ_ID AND ort.ID = adr.ORT_ID AND name.name = 'Meier' call count ------- -----Parse 1 Execute 1 Fetch 595 ------- -----total 597 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.07 0.17 3 295 3 0.00 0.00 0 0 0 4.19 11.87 12602 13187 20 -------- ---------- ---------- ---------- ---------4.26 12.04 12605 13482 23 rows ---------0 0 1784 ---------1784 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) Rows ------0 1784 624 1784 584 1784 296 1784 4 1784 2 2 1784 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: CHOOSE HASH JOIN TABLE ACCESS (FULL) OF 'ADR_VORWAHL' HASH JOIN TABLE ACCESS (FULL) OF 'ADR_PLZ' HASH JOIN TABLE ACCESS (FULL) OF 'ADR_ORT' HASH JOIN TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ANREDE' NESTED LOOPS TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_NAME' INDEX GOAL: ANALYZED (RANGE SCAN) OF 'ADR_NAME_IDX' (NON-UNIQUE) TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ADRESSEN' ****************************************************************************** Der Hash Zugriff ist schneller. ______________________________________________________________________________________________________ Oracle Trace Seite 69 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] Mit dem Einsatz des Hints FIRST_ROWS werden von Oracle grundsätzlich keine HASH JOINS eingesetzt, weil für den HASH erst einmal die Tabellen komplett gelesen werden müssen um im Anschluß den Hash Index zu Erzeugen, mit dem Oracle ab diesem Zeitpunkt auf die Konsistenten Daten der Tabelle im DB_BUFFER zugreifen wird. Unter dem Aspect, daß möglichst schnell die ersten Daten geliefert werden sollen, ist für den FIRST_ROWS ein HASH Zugriff nicht geeignet. Im folgenden Beispiel wurde der iniSID.ora Parameter gesetzt auf HASH_JOIN_ENABLED = TRUE. Das Statement mit dem Hint FIRST_ROWS eingesetzt. *************************************************************************************** SELECT /*+ FIRST_ROWS */ anr.ANREDE , name.Name , adr.VORNAME , adr.STRASSE , plz.plz , ort.ort , adr.TELEFON , vhl.vorwahl , adr.RUFNUMMER FROM LAMBERTZ.ADR_ADRESSEN adr , LAMBERTZ.ADR_anrede anr , LAMBERTZ.ADR_name name , LAMBERTZ.ADR_Ort ort , LAMBERTZ.ADR_plz plz , LAMBERTZ.ADR_Vorwahl vhl WHERE anr.ID = adr.ANREDE_ID AND vhl.ID = adr.VORWAHL_ID AND name.ID = adr.NAME_ID AND plz.ID = adr.PLZ_ID AND ort.ID = adr.ORT_ID AND name.name = 'Meier' call count ------- -----Parse 1 Execute 1 Fetch 595 ------- -----total 597 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.02 0.01 0 0 0 0.00 0.00 0 0 0 4.53 12.84 12609 26269 4 -------- ---------- ---------- ---------- ---------4.55 12.85 12609 26269 4 rows ---------0 0 1784 ---------1784 Misses in library cache during parse: 1 Optimizer goal: FIRST_ROWS Parsing user id: 21 (LAMBERTZ) Rows ------0 1784 1785 1785 1785 1785 2 2 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: HINT: FIRST_ROWS NESTED LOOPS NESTED LOOPS NESTED LOOPS NESTED LOOPS NESTED LOOPS TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_NAME' INDEX GOAL: ANALYZED (RANGE SCAN) OF 'ADR_NAME_IDX' (NON-UNIQUE) ______________________________________________________________________________________________________ Oracle Trace Seite 70 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] 1785 3568 3568 3568 3568 3568 3568 1784 3568 TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ADRESSEN' TABLE ACCESS (BY INDEX ROWID) OF 'ADR_ORT' INDEX (UNIQUE SCAN) OF 'ADR_ORT_ID_IDX' (UNIQUE) TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_ANREDE' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_ANREDE_ID_IDX' (UNIQUE) TABLE ACCESS (BY INDEX ROWID) OF 'ADR_PLZ' INDEX (UNIQUE SCAN) OF 'ADR_PLZ_ID_IDX' (UNIQUE) TABLE ACCESS (BY INDEX ROWID) OF 'ADR_VORWAHL' INDEX (UNIQUE SCAN) OF 'ADR_VORWAHL_ID_IDX' (UNIQUE) ****************************************************************************** Erstaunlich für den FIRST_ROWS Zugriff ist, daß die „elapsed time“ ähnlich ausfällt wie der HASH JOIN Zugriff, die Anzahl CONSISTENT GETS sich jedoch fast verdoppelt haben. Die CURRENT dagegen am geringsten ausfallen. Für Oracle Instanzen mit einer CPU, deren Kapazität durch weitere Oracle Instanzen oder sonstige Prozesse belastet sind, die eine hohe Anforderung an die CPU stellen, ist die HASH JOIN Methode langsamer, als ein Index Zugriff. Ursache ist die hohe CPU Anforderung. HASH JOIN Zugriffe bieten gerade dann einen zeitlichen Vorteil, wenn eine große Tabelle mit mehreren kleinen Tabellen gejoint wurde, wie folgender Vergleich zeigt. ****************************************************************************** SELECT FROM , , , WHERE AND AND AND plz.plz, ort.ort ADR_ADRESSEN adr ADR_NAME name ADR_Ort ort ADR_plz plz name.ID = adr.NAME_ID plz.ID = adr.PLZ_ID ort.ID = adr.ORT_ID name.name = 'Meier' call count ------- -----Parse 1 Execute 1 Fetch 595 ------- -----total 597 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.07 0 0 0 0.00 0.03 0 0 0 4.72 11.08 9872 13183 12 -------- ---------- ---------- ---------- ---------4.72 11.18 9872 13183 12 rows ---------0 0 1784 ---------1784 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) Rows ------0 1784 584 1784 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: CHOOSE HASH JOIN TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_PLZ' HASH JOIN ______________________________________________________________________________________________________ Oracle Trace Seite 71 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 296 1784 1 2 1493660 TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ORT' HASH JOIN TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_NAME' INDEX GOAL: ANALYZED (RANGE SCAN) OF 'ADR_NAME_IDX' (NON-UNIQUE) TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ADRESSEN' ****************************************************************************** SELECT /*+ FIRST_ROWS */ plz.plz, ort.ort FROM ADR_ADRESSEN adr , ADR_NAME name , ADR_Ort ort , ADR_plz plz WHERE name.ID = adr.NAME_ID AND plz.ID = adr.PLZ_ID AND ort.ID = adr.ORT_ID AND name.name = 'Meier' call count ------- -----Parse 1 Execute 1 Fetch 595 ------- -----total 597 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.01 0.01 0 0 0 0.00 0.00 0 0 0 51.73 65.65 9872 3007636 4 -------- ---------- ---------- ---------- ---------51.74 65.66 9872 3007636 4 rows ---------0 0 1784 ---------1784 Misses in library cache during parse: 1 Optimizer goal: FIRST_ROWS Parsing user id: 21 (LAMBERTZ) Rows ------0 1784 1785 1785 1493661 1495444 2987320 3568 3568 1784 3568 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: HINT: FIRST_ROWS NESTED LOOPS NESTED LOOPS NESTED LOOPS TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ADRESSEN' TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_NAME' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_NAME_ID_IDX' (UNIQUE) TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_ORT' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_ORT_ID_IDX' (UNIQUE) TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_PLZ' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_PLZ_ID_IDX' (UNIQUE) ****************************************************************************** Das gleiche Statement im HASH JOIN benötigt wesentlich weniger „consistent gets“ als der FIRST_ROWS Zugriff und zudem sechs mal schneller. ______________________________________________________________________________________________________ Oracle Trace Seite 72 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] 4.1.4. TABLE ACCESS (BY ROWID) Es erfolte ein Tabellenzugriff mit einer RowId als Einschränkung. Dies ist der schnellste Zugriff auf Daten einer Tabelle. Gewöhnlich erscheint diese Ausführungsbeschreibung durch den Einsatz eines Index, nicht aber, wenn in der WHERE Klausel eine Einschränkung über die RowID erfolgt. 4.1.5. INDEX (RANGE SCAN) Der wohl häufigste Zugriff über einen Index ist wohl der RANGE SCAN. Wie der Name schon vermuten läßt, wird ein Bereich des Index gelesen, um die Ergebnismenge im Anschluß aus der Datentabelle per ROWID zu ermitteln. Ein RANGE SCAN findet Einsatz, wenn alle Attribute eines Index in der Abfrage eingesetzt sind, aber auch wenn nur ein Teil der Attribute (entsprechend der definierten Reihenfolge) in der WHERE Klausel erscheinen. D.h., wenn ein Index aus den Attributen A, B, C besteht und nur die Attribute A, B in der WHERE Klausel abgefragt werden. Hierzu ein paar Beispiele: Für die Tabelle ADRESSEN liegt folgender Index vor: SQL > SELECT TABLE_NAME, COLUMN_NAME, COLUMN_POSITION FROM all_ind_columns WHERE index_name = 'ADRESSEN_IDX01'; TABLE_NAME -----------------------------ADRESSEN ADRESSEN COLUMN_NAME COLUMN_POSITION -------------- --------------PLZ 1 VORWAHL 2 Der folgende SELECT liefert einen RANGE SCAN ohne weiter auf die Datentabelle zugreifen zu müssen. Grund ist, daß alle Attribute des Index (in dem Fall „plz“) im Index enthalten sind. SQL > SELECT FROM WHERE AND plz adressen vorwahl = 7774 plz = 78267; Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Cost=3 Optimizer=CHOOSE 1 0 INDEX (RANGE SCAN) OF 'ADRESSEN_IDX01' Dieses Ergebnis liefert Ähnlichkeiten zu einem FAST FULL SCAN. Wesentlicher Unterschied ist, daß hier alle Attribute des Index in der WHERE Klausel zu finden sind. ______________________________________________________________________________________________________ Oracle Trace Seite 73 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] SQL > SELECT plz FROM adressen WHERE plz = 78267; Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Cost=18 Optimizer=CHOOSE 1 0 INDEX (RANGE SCAN) OF 'ADRESSEN_IDX01' Der Ausführungsplan gleicht dem zuvor ermittelten. Jedoch werden die COSTen um ein sechsfaches höher ausgewiesen. Grund liegt darin, daß der RANGE SCAN um ein vielfaches größer sein kann und somit auch die Ergebnismenge, gegenüber der Einschränkung mit allen Attributen des Index. Bedenkt man, daß die Tabelle ADRESSEN 1,6 Millionen Datensätze beinhaltet, ist die Bewertung mit COST=3 sehr gering. Ein idealer Zugriff ist demnach, wenn alle Attribute auf Gleichheit in der WHERE Klausel abgefragt werden. Somit ist die Annahme gerechtfertigt, daß zu einem erhöhten COST Wert ein nicht optimaler Zugriff gehört. Diese Verbindung gilt nur, wenn das SELECT ohne HINT gestartet wurde! 4.1.6. INDEX (UNIQUE SCAN) Dieser Zugriff wird ausgewiesen, wenn ein Index genau eine Rowid für die Tabelle ermittelt hat. Das ist in der Regel der Fall für einen UNIQUE Index. SELECT /*+ FIRST_ROWS */ PLZ FROM ADR_PLZ WHERE PLZ=1055; Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Cost=1 Optimizer=HINT: FIRST_ROWS 1 0 INDEX (UNIQUE SCAN) OF 'ADR_PLZ_IDX' (UNIQUE) 4.1.7. INDEX (FAST FULL SCAN) Mit einem FAST FULL SCAN werden die Indexblöcke, welche zur Ergebnisermiitlung gelesen werden nicht einzeln gelesen, sondern in Abhängigkeit des DB_FILE _MULTIBLOCK_READ_COUNT Parameter, wie er in der initSID.ora gesetzt ist. Der Optimizer benutzt diesen Index Zugriff gewöhnlich nur, wenn alle selektierten Attribute und die in der WHERE Klausel im Index enthalten sind. ______________________________________________________________________________________________________ Oracle Trace Seite 74 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Ein netter Nebeneffekt ist, das nicht alle Attribute in der WHERE Klausel erscheinen müssen, entsprechend der Reihenfolge der Attribute, damit der Index eingesetzt wird. Dies ist ein abweichendes Verhalten zu einem „gewöhnlichen“ Index. Zunächst einmal ein Beispiel: Für die Tabelle ADRESSEN liegt folgender Index vor: SQL > SELECT TABLE_NAME, COLUMN_NAME, COLUMN_POSITION FROM all_ind_columns WHERE index_name = 'ADRESSEN_IDX01'; TABLE_NAME -----------------------------ADRESSEN ADRESSEN COLUMN_NAME COLUMN_POSITION -------------- --------------PLZ 1 VORWAHL 2 Nachfolgender EXPLAIN PLAN zeigt, daß der Index im FAST FULL SCAN benutzt wird, obwohl das erste Attribut nicht in der WHERE Bedingung enthalten ist. SQL > SELECT plz FROM adressen WHERE vorwahl = 7774; Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Cost=1053 Optimizer=CHOOSE 1 0 SORT (AGGREGATE) 2 1 INDEX (FAST FULL SCAN) OF 'ADRESSEN_IDX01' Auch nachfolgendes SELECT Statement nutzt den FAST FULL SCAN mit gleichen COST Werten, denn ein Attribut auf Ungleichheit abgefragt ist, als wenn es gar nicht in der WHERE Bedingung enhalten wäre. SQL > SELECT FROM WHERE AND plz adressen vorwahl = 7774 plz != 78267; Execution Plan -------------------------------------------------------0 SELECT STATEMENT Cost=1053 Optimizer=CHOOSE 1 0 INDEX (FAST FULL SCAN) OF 'ADRESSEN_IDX01' Folgendes Statement führt unweigerlich zum FULL TABLE ACCESS, weil das selektierte Attribute „name“ nicht im Index enthalten ist: SQL > SELECT name, plz FROM adressen WHERE vorwahl = 7774; Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Cost=3664 Optimizer=CHOOSE 1 0 TABLE ACCESS (FULL) OF 'ADRESSEN' ______________________________________________________________________________________________________ Oracle Trace Seite 75 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] Viele Tests zeigen jedoch, daß ein FULL TABLE ACCESS meist bessere Antwortzeiten liefert, wenn die SGA großzügig dimensioniert ist und die Tabelle nicht gerade zu einem Dataware house gehört. 4.1.8. INDEX (FULL SCAN) Für ein SELECT ohne Einschränkung auf eine Tabelle wählt Oracle immer ein FULL TABLE ACCESS und verzichtet auf einen INDEX( FULL SCAN). Auch dann, wenn alle Index Attribute selektiert wurden. Ursache liegt darin, daß eine sequentielle Suche in in einer Tabelle einfacher und somit schneller erfolgen kann, als einen Index, der als B*Tree organisiert ist, vollständig zu lesen und anschließend die Daten der Tabelle. SQL > SELECT TABLE_NAME, COLUMN_NAME, COLUMN_POSITION FROM all_ind_columns WHERE index_name = 'ADRESSEN_IDX01'; TABLE_NAME -----------------------------ADRESSEN ADRESSEN COLUMN_NAME COLUMN_POSITION -------------- --------------PLZ 1 VORWAHL 2 Nachfolgender EXPLAIN PLAN zeigt, daß der Index nicht benutzt wird. SQL > SELECT plz, vorwahl FROM adressen; Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Cost=3664 Optimizer=CHOOSE 1 0 TABLE ACCESS (FULL) OF 'ADRESSEN' 4.1.9. INDEX (FULL SCAN (MIN/MAX)) Es wurde für die Ermittlung eines Min oder Max Wertes ein Index eingesetzt. Nicht zu erkennen ist, ob alle Attribute des Index für die Ermittlung eingesetzt wurden. An dieser Stelle hilft nur ein händische Prüfung. 4.1.10. CONNECT BY Für den Einsatz einer Hirarchieabfrage wird diese Ausführung angezeigt. ______________________________________________________________________________________________________ Oracle Trace Seite 76 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] 4.1.11. FILTER Innerhalb einer Verarbeitung entstehen Filteroperationen, wenn zwei Ergebnismengen zu einer zusammengeführt werden, indem die Ergebnisse, welche nicht der WHERE Einschränkung entsprechen, eleminiert werden. 4.1.12. MERGE JOIN Innerhalb einer Verarbeitung werden zwei Teilmengen gebildet, welche per MERGE JOIN zu einer zusammengeführt werden. 4.1.13. MERGE JOIN (OUTER) Innerhalb einer Verarbeitung werden zwei Teilmengen gebildet, welche per MERGE JOIN zu einer zusammengeführt werden. Jedoch sind diese Teilmengen entstanden aus einem Join, der als OUTER JOIN formuliert wurde. 4.1.14. MERGE JOIN (CARTESIAN) Kartesische Produkte entstehen immer dann wenn jeder Datensatz einer Tabelle gegen jeden Datensatz einer anderen Tabelle gejoint werden. Ein kartesisches Produkt behindert die Verarbeitung eines Statements. Wie aber entsteht es. Eine einfache Form ist folgendes Statement: SELECT emp.* FROM emp, dept Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Cost=5 Optimizer=CHOOSE 1 0 MERGE JOIN (CARTESIAN) 2 1 INDEX (FAST FULL SCAN) OF 'DEPT_PRIMARY_KEY' 3 1 SORT (JOIN) 4 3 TABLE ACCESS (FULL) OF 'EMP' Die Ursache liegt in der fehlenden oder unzureichenden Einschränkung des Statements in der WHERE Klausel. Nachfolgend ein Statement mit durchgängiger Einschränkung in der Where Klausel. SELECT /*+ FIRST_ROWS */ ort.ort FROM ADR_ADRESSEN adr , ADR_Ort ort ______________________________________________________________________________________________________ Oracle Trace Seite 77 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] , , WHERE AND AND AND ADR_plz plz ADR_NAME name name.name = 'Meier' name.ID = adr.NAME_ID plz.ID = adr.PLZ_ID ort.ID = adr.ORT_ID; Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Cost=1497273 Optimizer=HINT: FIRST_ROWS 1 0 NESTED LOOPS 2 1 NESTED LOOPS 3 2 NESTED LOOPS 4 3 TABLE ACCESS (FULL) OF 'ADR_ADRESSEN' 5 3 TABLE ACCESS (BY INDEX ROWID) OF 'ADR_NAME' 6 5 INDEX (UNIQUE SCAN) OF 'ADR_NAME_ID_IDX' 7 2 TABLE ACCESS (BY INDEX ROWID) OF 'ADR_ORT' 8 7 INDEX (UNIQUE SCAN) OF 'ADR_ORT_ID_IDX' 9 1 TABLE ACCESS (BY INDEX ROWID) OF 'ADR_PLZ' 10 9 INDEX (UNIQUE SCAN) OF 'ADR_PLZ_ID_IDX' Die Bearbeitung des Statements erfolgt in folgenden Schritten: adr.name -> adr.name_id -> name.id -> adr.ort_id -> ort.id -> ort.ort Werden nun Teile aus der WHERE Klausel entfernt, fehlen die einzelnen Verbindungen von einer zur anderen Tabelle. Diese Lücken werden geschlossen, indem Oracle alle Daten einer Tabelle liest, für die keine Eischränkung vorliegt und „merge join“ diese zu einem Ergebnis. SELECT FROM , , , WHERE /*+ FIRST_ROWS */ plz.plz, ort.ort ADR_ADRESSEN adr ADR_Ort ort ADR_plz plz ADR_NAME name name.name = 'Meier'; Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Cost=885769941 Optimizer=HINT: FIRST_ROWS 1 0 MERGE JOIN (CARTESIAN) 2 1 MERGE JOIN (CARTESIAN) 3 2 MERGE JOIN (CARTESIAN) 4 3 INDEX (RANGE SCAN) OF 'ADR_NAME_IDX' 5 3 SORT (JOIN) 6 5 TABLE ACCESS (FULL) OF 'ADR_ORT' 7 2 SORT (JOIN) 8 7 TABLE ACCESS (FULL) OF 'ADR_ADRESSEN' 9 1 SORT (JOIN) 10 9 TABLE ACCESS (FULL) OF 'ADR_PLZ' Im gezeigten Beispiel ist gut zu erkennen, daß für jede fehlende Bedingung in der WHERE Klausel ein Merge Join (cartesian) entsteht. ______________________________________________________________________________________________________ Oracle Trace Seite 78 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Aber Vorsicht. Oracle erkennt eine CARTESIAN nicht immer !!! 4.1.15. SORT (ORDER BY) In einem Statement wurde eine ORDER BY Klausel angegeben. Dies spiegelt sich in einem Ausführungsplan wieder. 4.1.16. SORT (JOIN) Bevor ein MERGE JOIN ausgeführt werden kann erfolgt die Sortierung der einzelnen Ergebnismengen in einem SORT (JOIN). 4.1.17. SORT (AGGREGATE) In einem Statement wurde eine GROUP BY Klausel angegeben. Für die Ermittlung des Result Set benutzt Oracle selbst jedoch eine GROUP BY Funktion. 4.1.18. SORT (UNIQUE) Innerhalb der Bildung einer Ergebnismenge erfolgt die Eleminierung doppelter Einträge per SORT (UNIQUE). Meist zu finden für den Einsatz einer DISTINCT Funktion in einem SELECT Statement. 4.1.19. SORT (GROUP BY) In einem Statement wurde eine GROUP BY Klausel angegeben. Innerhalb der Gruppierung erfolgt eine. SQL > SELECT FROM , , , WHERE AND AND AND GROUP plz.plz, ort.ort ADR_ADRESSEN adr ADR_Ort ort ADR_plz plz ADR_NAME name name.name = 'Meier' name.ID = adr.NAME_ID plz.ID = adr.PLZ_ID ort.ID = adr.ORT_ID BY plz.plz, ort.ort; ______________________________________________________________________________________________________ Oracle Trace Seite 79 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Cost=3080 Optimizer=HINT: FIRST_ROWS 1 0 SORT (GROUP BY) 2 1 HASH JOIN 3 2 TABLE ACCESS (FULL) OF 'ADR_PLZ' 4 2 HASH JOIN 5 4 TABLE ACCESS (FULL) OF 'ADR_ORT' 6 4 HASH JOIN 7 6 TABLE ACCESS (BY INDEX ROWID) OF 'ADR_NAME' 8 7 INDEX (RANGE SCAN) OF 'ADR_NAME_IDX' 9 6 TABLE ACCESS (FULL) OF 'ADR_ADRESSEN' 4.1.20. NESTED LOOPS (OUTER) Für den verwendeten Join handelt es sich um einen OuterJoin 4.1.21. NESTED LOOPS (ANTI) Für den verwendeten Join handelt es sich um einen AntiJoin 4.1.22. NESTED LOOPS (SEMI) Für den verwendeten Join handelt es sich um einen SemiJoin 4.1.23. CONCATENATION Im SELECT liegt eine CONCATENATION Verknüpfung vor. 4.1.24. INTERSECTION Im SELECT liegt eine INTERSECTION Verknüpfung vor. 4.1.25. MINUS Im SELECT liegt eine MINUS Verknüpfung vor. 4.1.26. UNION Im SELECT liegt eine UNION Verknüpfung vor. 4.1.27. UNION-ALL Im SELECT liegt eine UNION-ALL Verknüpfung vor. ______________________________________________________________________________________________________ Oracle Trace Seite 80 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] 4.1.28. VIEW Entweder befindet sich eine VIEW in der Abfrage oder Oracle bildetete zur Laufzeit eine temporäre Tabelle und weist diese als VIEW aus. *************************************************************************************** SELECT a.name, b.maximal FROM (SELECT MAX( plz ) maximal FROM adr_plz ) b, adressen a call count ------- -----Parse 1 Execute 1 Fetch 70 ------- -----total 72 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 0.00 0.01 0 0 0 0.01 0.00 0 74 8 -------- ---------- ---------- ---------- ---------0.01 0.01 0 74 8 rows ---------0 0 210 ---------210 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) Rows ------0 36 1 1 1 36 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: CHOOSE MERGE JOIN (CARTESIAN) VIEW SORT (AGGREGATE) INDEX GOAL: ANALYZED (FULL SCAN (MIN/MAX)) OF 'ADR_PLZ_IDX' (UNIQUE) TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADRESSEN' ****************************************************************************** 4.1.29. NESTED LOOPS Zu einem NESTED LOOPS gehören mindestens zwei Tabellen (Teilmengen), wovon die Eine, mit hilfe eines Index, die Ergebnismenge gegen die zweite Tabelle vergleicht. SELECT anr.ANREDE , name.Name , adr.VORNAME , adr.STRASSE , plz.plz , ort.ort , adr.TELEFON , vhl.vorwahl , adr.RUFNUMMER FROM LAMBERTZ.ADR_ADRESSEN adr , LAMBERTZ.ADR_anrede anr , LAMBERTZ.ADR_name name , LAMBERTZ.ADR_Ort ort , LAMBERTZ.ADR_plz plz , LAMBERTZ.ADR_Vorwahl vhl WHERE anr.ID = adr.ANREDE_ID AND vhl.ID = adr.VORWAHL_ID AND name.ID = adr.NAME_ID AND plz.ID = adr.PLZ_ID ______________________________________________________________________________________________________ Oracle Trace Seite 81 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] AND ort.ID = adr.ORT_ID AND name.name = 'Meier' Rows ------0 1784 1785 1784 1785 1784 1785 1784 1785 -> 1784 2 2 1784 1784 4 1784 296 1784 584 1784 624 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: CHOOSE MERGE JOIN SORT (JOIN) MERGE JOIN SORT (JOIN) MERGE JOIN SORT (JOIN) MERGE JOIN SORT (JOIN) NESTED LOOPS TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_NAME' INDEX GOAL: ANALYZED (RANGE SCAN) OF 'ADR_NAME_IDX' (NON-UNIQUE) TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ADRESSEN' SORT (JOIN) TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ANREDE' SORT (JOIN) TABLE ACCESS (FULL) OF 'ADR_ORT' SORT (JOIN) TABLE ACCESS (FULL) OF 'ADR_PLZ' SORT (JOIN) TABLE ACCESS (FULL) OF 'ADR_VORWAHL' Bearbeitet wird als erstes die Tabelle ADR_NAME indem der Index ADR_NAME_IDX eine Auswahl an RowID’s bestimmt hat, die der Bedingung <name.name=‘Meier‘> entspricht und aus der Tabelle selektiert werden per ROWID. Jeder Satz aus der Tabelle ADR_NAME wird als Einschränkung (zwei Sätze) genutzt um die Ergebnismenge der Tabelle ADR_ADRESSEN zu bestimmen (1784 Sätze). Diese Anzahl entspricht dem Ergebnis des NESTED LOOPS. In diesem Fall wird der TABLE ACCESS FULL nur einmal! (auch wenn zwei Rows für die Tabelle ADR_NAME im Plan ausgewiesen werden) ausgeführt und liefert jeweils 1784 Ergebnissätze an die. Hier der Beweis. SELECT * FROM LAMBERTZ.ADR_name name WHERE name.name = 'Meier'; ID NAME ---------- -------------------------------------------------------------155936 Meier 1 row selected. SQL > **************************************************************************** SELECT * FROM adr_name WHERE name = 'Meier' ______________________________________________________________________________________________________ Oracle Trace Seite 82 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] call count ------- -----Parse 1 Execute 1 Fetch 1 ------- -----total 3 cpu elapsed disk query current -------- ---------- -------- -------- ---------0.01 0.00 0 0 0 0.00 0.00 0 0 0 0.00 0.00 0 4 0 -------- ---------- -------- -------- ---------0.01 0.00 0 4 0 rows -------0 0 1 -------1 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) Rows ------0 1 2 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: CHOOSE TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_NAME' INDEX GOAL: ANALYZED (RANGE SCAN) OF 'ADR_NAME_IDX' (NON-UNIQUE) **************************************************************************** Oracle liefert genau einen Satz. Somit stellt -vom Sinn her- u.a Statement den zweiten Tabellenzugriff in der NESTED LOOPS dar. SELECT , , , FROM , WHERE adr.VORNAME adr.STRASSE adr.TELEFON adr.RUFNUMMER LAMBERTZ.ADR_ADRESSEN adr LAMBERTZ.ADR_name name adr.NAME_ID = 155936 Das Result Set liefert auch genau die im Trace angegeben 1784 Datensätze. In diesem Zusammenhang ist auch ersichtlich, warum Oracle diese Tabelle als treibende Tabelle ausgewählt hat. Auch wenn ADR_ANREDE mit maximal 3 Einträgen als führende Tabelle wesentlich effektiver wäre. Sind zwei NESTED LOOPS in einander verschachtelt, werden erst alle Ergebnisse der inneren Schleife gebildet und als Ergebnismenge mit dem äußeren Loop -gewöhnlich eine weitere Tabelle- verglichen und ausgewertet. 4.1.30. HASH JOIN Als Gegenteil zu einem NESTED LOOP ist ein HASH JOIN Zugriff zu sehen. Für HASH JOINs werden Tabellen im FULL TABLE gelesen oder zuvor ermittelte Ergebnisse nach dem HASH Algorithmus im Memory verarbeitet. ______________________________________________________________________________________________________ Oracle Trace Seite 83 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 4.2. INDEX RANGE SCAN In fast allen EXPLAIN PLAN Ausgaben wird ein Index Zugriff als RANGE SCAN angegeben. RANGE SCANs sind im gewöhnlich nur langsam, wenn viele Datensätze des Index gelesen werden müssen. Ein RANGE SCAN wird auch angezeigt, nur ein Treffer aus der Tabelle geliefert wird. Hier ein Beispiel: *************************************************************************************** SELECT * FROM ADR_name name WHERE name.name = 'Meier' call count ------- -----Parse 1 Execute 1 Fetch 1 ------- -----total 3 cpu elapsed disk query current -------- ---------- ------- ---------- ---------0.00 0.00 0 0 0 0.00 0.00 0 0 0 0.00 0.00 0 4 0 -------- ---------- ------- ---------- ---------0.00 0.00 0 4 0 rows ------0 0 1 ------1 Misses in library cache during parse: 0 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) Rows ------0 1 2 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: CHOOSE TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_NAME' INDEX GOAL: ANALYZED (RANGE SCAN) OF 'ADR_NAME_IDX' (NON-UNIQUE) *************************************************************************************** Selbst für einen UNIQUE INDEX erfolgen wenigstens zwei Index Zugriffe für einen Wert aus der Tabelle: *************************************************************************************** SELECT * FROM adr_anrede WHERE anrede = 'Herrn' call count ------- -----Parse 1 Execute 1 Fetch 1 ------- -----total 3 cpu elapsed disk query current -------- ---------- ------- ---------- ---------0.01 0.01 0 0 0 0.00 0.00 0 0 0 0.01 0.06 1 2 0 -------- ---------- ------- ---------- ---------0.02 0.07 1 2 0 rows -------0 0 1 -------1 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) ______________________________________________________________________________________________________ Oracle Trace Seite 84 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Rows ------0 1 2 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: CHOOSE TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_ANREDE' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_ANREDE_IDX' (UNIQUE) *************************************************************************************** Ein weiteres Beispiel zeigt das gleiche Ergebnis ****************************************************************************** SELECT /*+ RULE */ FROM adr_plz WHERE id=10 call count ------- -----Parse 1 Execute 1 Fetch 1 ------- -----total 3 * cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 0.00 0.00 0 0 0 0.00 0.02 2 3 0 -------- ---------- ---------- ---------- ---------0.00 0.02 2 3 0 rows ---------0 0 1 ---------1 Misses in library cache during parse: 1 Optimizer goal: RULE Parsing user id: 21 (LAMBERTZ) Rows Execution Plan ------- --------------------------------------------------0 SELECT STATEMENT GOAL: HINT: RULE 1 TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_PLZ' 2 INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_PLZ_ID_IDX' (UNIQUE) ****************************************************************************** An diesem Trace ist deutlich zu erkennen, daß ORACLE aus dem Index 2 Sätze liest und einen Datensatz aus der Tabelle. Nur mit einem ROWNUM=1 wird nur ein Staz aus dem Index gelesen. *************************************************************************************** SELECT /*+ RULE */ * FROM adr_plz WHERE id=10 AND rownum =1 call count ------- -----Parse 1 Execute 1 Fetch 1 ------- -----total 3 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 0.00 0.00 0 0 0 0.00 0.00 0 3 0 -------- ---------- ---------- ---------- ---------0.00 0.00 0 3 0 rows ---------0 0 1 ---------1 Misses in library cache during parse: 1 Optimizer goal: RULE Parsing user id: 21 (LAMBERTZ) Rows ------0 1 1 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: HINT: RULE COUNT (STOPKEY) TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_PLZ' ______________________________________________________________________________________________________ Oracle Trace Seite 85 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 1 INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_PLZ_ID_IDX' (UNIQUE) *************************************************************************************** Es wird deutlich, wie Oracle einen B*Tree Index auswertet. Erst wird die ID gelesen, welche der WHERE Bedingung entspricht und mit der darauffolgenden Row wird sichergestellt, daß ein Wechel des Kriteriums vorliegt. Durch die Sortierung eines Index ist sichergestellt, das nicht an einer anderen Stelle im Index ein Eintrag der WHERE Bedingung entsprechen würde. ______________________________________________________________________________________________________ Oracle Trace Seite 86 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] 5. Bedeutung von Tabellen und Index Statistiken Bei der Auswertung einer Trace oder TKProf Datei bleiben für die Interpretation immer Grauzonen. Statistiken helfen diese zu erkennen. Eine gute Möglichkeit bieten die Oracle Utilities UTLBSTAT.SQL und UTLESTAT.SQL. Mit UTLBSTAT.SQL werden alle relevanten Daten in temporäre Tabellen eingetragen und mit den Werten abgeglichen, die mit UTLESTAT.SQL ermittelt werden. Diese Scripte bieten den Vorteil, daß Statistiken für einen beliebigen Zeitraum erstellt werden können. Alle V$ Tabellen, in denen Statistiken angezeigt werden, beziehen sich entweder auf eine Session oder auf Gesamtergebnisse seit dem letzten Start der Instanz. Führt man UTLBSTAT.SQL in SQL*Plus aus und direkt danach UTLESTAT.SQL werden Egebnisse aufgelistet, obwohl zwischenzeitlich keine anderen Operationen auf der Datenbank vorgenommen wurden. Das liegt an SQL*Plus. Verwendet man ein anderes Programm, indem die Differenzbildung vorgenommen wird, sind diese Verfälschungen nicht mehr vorhanden. ______________________________________________________________________________________________________ Oracle Trace Seite 87 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 6. Glossar 6.1. Recursive calls werden z.B. in SQL*Plus angezeigt wenn AUTOTRACE eingeschaltet wurde. Recursive calls geben eine Aussage, wie oft Statements implizit ausgeführt wurden (entweder von Oracle oder Statements aus einer Procedure). Bei solchen Statements handelt es sich DLL Kommandos, die genau wie alle anderen Statements geparsed werden müssen, z.B. durch die Einbindung eines weiteren Extents. Aber auch, wenn ein Cursor für ein Statement von Oracle während der Verarbeitung aus dem Cache entfernt und immer wieder zu Ausführung geladen werden muß. Auch der Zugriff auf das Data Dictonary äußert sich als recursive call. Die Anzahl der recursive calls sollte nach Möglichkeit 0 sein, wenn keine Proceduren, Funktionen eingesetzt wurden. Trigger für Datentabellen produzieren auch Recursive Calls. 6.2. DB_BLOCK_SIZE Ist die Blockgröße, in der Oracle seine Daten innerhalb eines Extents und im Speicher verwaltet. Die Blockgröße sollte mit der des Betriebsystems übereinstimmen, da sonst ein Datenüberhang entsteht. Die Blockgröße kann nur beim Erstellen der Datenbank festgelegt werden und ist danach nicht mehr änderbar! 6.3. DB_FILE_MULTIBLOCK_READ_COUNT DB_FILE_MULTIBLOCK_READ_COUNT ist ein initSID.ora Parameter und beschreibt die Anzahl Datenblöcke, die für einen einzigen Lesezugriff zusammenhängend gelesen werden können. Diese Angabe wirkt nur für FULL TABLE ACCESS Zugriffe oder dem Index Zugriff FAST FULL SCAN. Alle anderen Zugriffe basieren immer auf einem Block. Beinhaltet die Tabelle weiniger Blöcke als die durch den Parameter DB_FILE_MULTIBLOCK_READ_COUNT festgelegte Anzahl, werden die verfügbaren in einem I/O Zugriff eingelesen. Für diesen Parameter gibt es keine allgemeingültigen Richtwert. Wurde er zu gering gewählt, werden wenige Datenblöcke mit einem Lesezugriff in den Memory gelesen und jede weitere Anforderung führt zu einem I/O und damit im schlechtesten Fall zu Wartezeiten. Aber auch eine zu großzügige Definition des Parameters DB_FILE_MULTIBLOCK_READ_COUNT kann dazu führen, ______________________________________________________________________________________________________ Oracle Trace Seite 88 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] daß der verfügbare Memory nicht ausreicht, die für einen I/O gelieferten Datenmengen aufzunehmen. Übermäßiges „flash out“ ist die Folge. Ein Hinweis, ob dieser Parameter richtig oder falsch gewählt wurde, ist zu erkennen an dem Wert DB_FILE_SEQUENTIAL_READ in der View V$SYSTEM_EVENT (SELECT * FROM V$SYSTEM_EVENT WHERE name = ‘ db file sequential read‘). Er besagt, wie lange und oft Oracle warten mußte, einen angeforderten Block aus dem Datafile zu erhalten. Mit einem größeren Wert für DB_FILE_MULTIBLOCK_READ_COUNT werden also mehere Blöcke direkt in einem Lesevorgang in den DB_BUFFER übertragen. Der Wert für DB_FILE_SEQUENTIAL_READ sollte möglichst 0 sein. In jedem Fall sollte er kleiner werden, wenn DB_FILE_MULTIBLOCK_READ_COUNT vergrößert wird. 6.4. Chained rows treten auf, wenn bei einem INSERT oder UPDATE der verfügbare Speicher nicht ausreicht, um die veränderten Daten im gleichen Block zu speichern. Die Blockgröße ist abhängig von der Definition DB_BLOCK_SIZE in der initSID.ora Datei. CHAINED ROWS können häufig vermieden werden, wenn der Wert PCTFREE einer Tabelle vergrößert wird. Ist ein Datensatz jedoch größer als das gesamte Block, entsteht immer ein CHAINED ROW. Beispiel: DB_BLOCK_SIZE ist für die Datenbank mit 4096 (Byte) eingestellt und für eine Tabelle wurde PCTUSED mit 40 und PCTFREE mit 10 festgelegt. Somit stehen maximal ca.3686 Bytes an Speicherplatz für einen Block zur Verfügung. Nun werden drei Records mit jeweils 1200 Byte in einem Block gespeichert. Erfolgt ein UPDATE am ersten Datensatz, der um 390 Byte verlängert wird, ist der PCTFREE ist mit 410 Bytes groß genug um diese Änderung im gleichen Block einzutragen. Für das gleiche UPDATE an den beiden anderen Datensätzen reicht der reservierte Platz nicht mehr aus. Zwei CHAINED ROWS sind entstanden. Wäre PCTFREE mit 30 (1229 Bytes) bestimmt worden, hätte ORACLE nicht drei, sondern nur zwei Datensätze der Größe 1200 Bytes in dieses Block eingetragen und die Satzerweiterung von gesamt 780 Bytes in dem PCTFREE aufnehmen können. Unter den gleichen Bedigungen wie in vorherigem Beispiel soll nun ein Datensatz mit der Gesamtlänge von 9870 Byte eingetragen werden. Auch wenn PCTFREE auf 0 gesetzt wäre, reicht der verfügbare Speicherplatz in einem Block nicht aus den Datensatz zu speichern. ______________________________________________________________________________________________________ Oracle Trace Seite 89 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 6.5. Cost Diese Maßzahl ist ein theoretischer Wert. Jeder Zugriffsmethode ist ein Wert zugeordnet. Der schnellste Zugriff ist der auf eine Zeile per ROWID, wogegen der langsamste ein TABLE ACCESS FULL ist. Auch ohne genaue Werte zu kennen gilt (theoretisch): geringe Kosten - guter Zugriffspfad hohe Kosten - schlechter Zugriffspfad An diesen Kosten orientiert sich auch der Oracle Optimizer für die Bestimmung des Explain Plan. Jedoch gibt es unzählige Beispiele, wo trotz sehr hoher Kosten eine schnellere Abarbeitung in der Datenbank erfolgt. Also Vorsicht!! 6.6. DB Block gets zeigt die Anzahl Blöcke, die als CONSISTENT GETS in den CURRENT Mode übertragen wurden. 6.7. DB Block changes beschreibt die Anzahl dirty Blocks, welche durch INSERT, DELETE oder UPDATE in der SGA (DB_BUFFER) verändert und ins Rollback geschrieben wurden. ______________________________________________________________________________________________________ Oracle Trace Seite 90 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] 6.8. Datenblöcke im Überblick In der folgenden Grafik werden die Zusammenhänge deutlich, so wie sie in den oberen Kapiteln beschrieben wurden. Es handelt sich immer um Datenblöcke von der Größe, wie sie mit dem Parameter DB_BLOCK_SIZE in der initSID.ora festgelegt wurden. Physikalisches Device Physikalisches Device Tabellen Indizes Rollback Consistent Change Rollbackdaten DB_BUFFER Consistent Gets DB BLOCK GETS DB BLOCK CHANGES Current Blocks Result Set ______________________________________________________________________________________________________ Oracle Trace Seite 91 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 6.9. Extents Die Anzahl der EXTENTs spiegelt sich in der HIGH WATER MARK wieder und umgekehrt. Die Anzahl EXTENTs einer Tabelle verringert sich nicht durch das Löschen von Datensätzen in der Tabelle, weil Oracle sich an der HIGH WATER MARK orientiert. EXTENTs können nicht explizit gelöscht werden, sondern verfallen durch ein TRUNC auf die entsprechende Tabelle. Die Anzahl der EXTENTs ergibt sich aus dem größten Datenvolumen, was zu irgend einem Zeitpunkt den Füllstand der Tabelle ausgemacht hat, der HIGH WATER MARK. 6.10. High Water Mark ist eine Maßzahl für den letzten Datenblock einer Tabelle im letzten Extent in der Datenbank, der zu irgend einem Zeitpunkt einmal Daten beinhaltet hat. Diese Marke wird selbst durch Löschen aller Datensätzen nicht herunter gesetzt, sondern bleibt bis zu einem TRUNCATE oder DROP der Tabelle bestehen. Bei einen FULL TABLE ACCESS Zugriff auf Daten einer Tabelle werden alle Blöcke bis zur HIGH WATER MARK eingelesen; auch wenn sich im Extremfall nicht ein Datensatz in der Tabelle befindet. Die HIGH WATER MARK erhöht sich auch, wenn Daten in die Tabelle eingefügt werden und per ROLLBACK wieder entfernt wurden. Parallele Inserts in Tabellen produzieren ggf. eine sehr hoch gelegene HIGH WATER MARK. Ein FULL TABLE ACCESS arbeitet die gesamte Tabelle bis zur HIGH WATER MARK ab, auch wenn nicht ein Datensatz in der Tabelle vorliegt. Änderungen an der HIGH WATER MARK finden auch statt wenn Daten per Rollback wieder aus der Tabelle entfernt wurden! 6.11. User calls sind Anforderungen durch Oracle dem User Sources bereitzustellen. User Calls beinhalten Parse und Execute Anforderungen. ______________________________________________________________________________________________________ Oracle Trace Seite 92 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] 6.12. Execute counts Gesamtanzahl aller SQL Aufrufen (User und recursive calls). 6.13. Distinct keys Wird immer nur für einen Index angegeben und zeigt die Anzahl Datensätze an, die mit einem Index Key erreicht werden können. Der Wert beträgt für einen primary Key die Anzahl Rows der zugehörigen Tabelle. Im schlechtesten Fall liegt der Wert bei eins und würde aussagen, daß für einen Index Key alle Sätze der Tabelle angesprochen würden. Gewöhnlich wird der Wert des DISTINCT KEY jedoch berechnet. Z.B. sind in der Tabelle ADR_ADRESSEN 245591 NAME_ID’s vor: SQL > select count( distinct name_id ) from adr_adressen; COUNT(DISTINCT NAME_ID) ---------------------245591 1 row selected. Diese Werte werden von Oracle ermittelt, wenn ein Index mit Statistikwerten durch ANALYZE bestimmt wurden. In der View ALL_INDEXES existiert eine Spalte mit dem Namen DISTINCT_KEYS, in dem der Wert gespeichert ist. Abweichungen zwischen dem errechneten Wert und dem Wert, wie er in der View ALL_INDEXES eingetragen ist, entstehen durch die Art, wie die Statistikwerte ermittelt wurden. Nur mit der ANALYZE Option COMPUTE liegen keine Abweichungen vor. Oracle diese Arbeit leisten zu lassen, hat u.a. den Vorteil, daß DISTINCT_KEYS für Indizes ermittelt werden, die aus mehreren Attributen bestehen. 6.14. Cardinalität Errechnet sich aus: ______________________________________________________________________________________________________ Oracle Trace Seite 93 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] Gesamte Anzahl Datensätze einer Tabelle -------------------------------------------------------------------------------------- DISTINCT_KEYS Also im besten Fall ergibt sich der Wert 1 und im schlechtesten fall die Anzahl der Datensätze der Tabelle. 6.15. Rule Die gesamte Vorgehensweise die Oracle benutzt, ein Result Set zu ermitteln, basiert auf einem Regelwerk. In diesem Regelwerk wird beschrieben welche Operationen als schnell und unkritisch gelten und welche nicht von Vorteil sind. Priorität Zugriffsmethode 1 Tabellenzugriff Zugriff per RowID 2 Tabellenzugriff mit einem Unique Index (ein Attribut), für den das IndexAttribut in der WHERE Klausel auf = abgefragt wird. 3 Tabellenzugriff mit einem Unique Index (konkateniert), für den alle IndexAttribute in der WHERE Klausel auf = abgefragt werden. 4 für den alle IndexAttribute in der WHERE Klausel auf = abgefragt gegen einen Cluster Key des selben Clusters 5 Tabellenzugriff mit einem Cluster-Index für den alle IndexAttribute in der WHERE Klausel auf = abgefragt werden. 6 IndexSchlüssel eines Hash Clusters Tabellenzugriff mit einem NonUnique Index (konkatieniert), für den alle IndexAttribute in der WHERE Klausel auf = abgefragt werden. 7 Tabellenzugriff mit einem NonUnique Index für ein Attribut. 8 B*tree Index mit mehreren Attributen einer Tabelle (zusammengesetzter Index). Tabellenzugriff mit einem uncompressed Index (konkatiniert), für den alle IndexAttribute in der WHERE Klausel auf >= oder <= abgefragt werden. 9 Tabellenzugriff mit einem compressed Index (konkatiniert), für den alle IndexAttribute in der WHERE Klausel auf >= oder <= ______________________________________________________________________________________________________ Oracle Trace Seite 94 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] den alle IndexAttribute in der WHERE Klausel auf >= oder <= abgefragt werden. 10 Bereichssuche in einem abgeschlossenen Intervall über Spalten mit einem Index. Tabellenzugriff mit einem uncompressed Index (konkatiniert), für den nicht alle IndexAttribute in der WHERE Klausel abgefragt werden. 11 Bereichsuche in einem nicht abgeschlossenen Intervall über Spalten mit einem Index Tabellenzugriff mit einem compressed Index (konkatiniert), für den nicht alle IndexAttribute in der WHERE Klausel abgefragt werden. 12 Tabellenzugriff mit einem Unique Index für BETWEEN oder LIKE Abfragen 13 Tabellenzugriff mit einem NonUnique Index für BETWEEN oder LIKE Abfragen 14 Tabellenzugriff mit einem Unique Index für > oder < Abfragen 15 Tabellenzugriff mit einem NonUnique Index für > oder < Abfragen 16 Sort / Merge Operationen 17 Max oder Min Ermittlung auf Spalten einer Tabelle, für die ein Index vorhanden ist. 18 ORDER BY auf Spalten eines Index, GROUP BY auf Spalten eines Index 19 Full Table Access, ORDER BY auf Spalten ohne Index, GROUP BY auf Spalten ohne Index Für einen regelbasierten Zugriff muß das eingentliche Statement auch einem Regelwerk entsprechen. Die treibende Tabelle muß als letzte Tabelle in der FROM Klausel aufgezeigt werden und das Attribut, mit welchen Oracle die erste Teilmenge bestimmen soll, ist an erster Stelle der WHERE Klausel anzugeben (siehe auch Kapitel „Wie erfolgt die Auflösung eines Statement“ ). SELECT FROM WHERE AND * Tablle1, Tabelle2(treibene Tabelle) Tabelle2.bez = ‘Weiss nicht‘ Tabelle2.Tabelle1_id =Tabelle1.id ______________________________________________________________________________________________________ Oracle Trace Seite 95 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 6.16. Choose Seit der Version 7.xx stellt Oracle zu dem RULE Mode den statistikbasierten Zugriff zur Verfügung. Er kann gewählt werden per Hint, ALTER SESSION.., oder über eine Einstellung in der initSID.ora und die gesamte Datenbank arbeitet im CHOOSE Mode. CHOOSE bedeutet, daß der statistikbasierte Zugriff gewählt wird, wenn für die am Statement beteiligten Tabellen und Indizes Statistikwerte erzeugt wurden. Wenn keine Statistiken für die Tabellen und Indizes vorliegen erfolgt der Zugriff im RULE Mode, auch wenn die Datenbank für den CHOOSE Mode eingestellt ist. So oder ähnlich wird die Frage beantwortet, ob die Ausführung eines Statement regel- oder statistikbasiert ohne Statistikwerte erfolgt. Betrachten wir folgende Ausgabgssituation: SELECT FROM WHERE OR index_name, column_name, column_position user_ind_columns table_name = 'ADR_ADRESSEN' table_name = 'ADR_NAME'; TABLE_NAME -------------ADR_ADRESSEN ADR_NAME ADR_NAME INDEX_NAME --------------------ADR_ADRESSEN_ID_IDX ADR_NAME_IDX ADR_NAME_ID_IDX COLUMN_NAME ----------------ID NAME ID COLUMN_POSITION --------------1 1 1 SQL > SELECT count(*) FROM adr_adressen; COUNT(*) ---------1493660 1 row selected. SQL > SELECT count(*) FROM adr_name; COUNT(*) ---------280073 1 row selected. SQL > Wenn also im CHOOSE Mode ein RULE Zugriff erfolgt, sollte man das am Explain Plan erkennen können. Für einen RULE Zugriff werden keine COSTen angezeigt und die Anzahl Buffer Gets sind dann gleich. ______________________________________________________________________________________________________ Oracle Trace Seite 96 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Für das erste Statement wurde sichergestellt, daß auf beiden Tabellen Statistikwerte vorliegen. Die Ausführung erfolgt ohne Hint. Mit Statistikwerten: SQL > select * from adr_adressen, adr_name where adr_name.name like 'Meyer%' and adr_adressen.name_id = adr_name.id and rownum < 11; Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Cost=3024 Optimizer=CHOOSE 1 0 COUNT (STOPKEY) 2 1 HASH JOIN 3 2 TABLE ACCESS (BY INDEX ROWID) OF 'ADR_NAME' 4 3 INDEX (RANGE SCAN) OF 'ADR_NAME_IDX' 5 2 TABLE ACCESS (FULL) OF 'ADR_ADRESSEN' Statistics ---------------------------------------------------------0 recursive calls 4 db block gets 89 consistent gets Um erkennen zu können, wann ein RULE Zugriff im CHOSSE Mode erfolgt, wird das folgende Statement per Hint zu einem RULE Zugiff gezwungen. Rule Zugriff mit einem Hint erzwungen: SQL > select /*+ RULE */ * from adr_adressen, adr_name where adr_name.name like 'Meyer%' and adr_adressen.name_id = adr_name.id and rownum < 11; Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Optimizer=HINT: RULE 1 0 COUNT (STOPKEY) 2 1 NESTED LOOPS 3 2 TABLE ACCESS (FULL) OF 'ADR_ADRESSEN' 4 2 TABLE ACCESS (BY INDEX ROWID) OF 'ADR_NAME' 5 4 INDEX (UNIQUE SCAN) OF 'ADR_NAME_ID_IDX' Statistics ---------------------------------------------------------0 recursive calls 4 db block gets 20844 consistent gets ______________________________________________________________________________________________________ Oracle Trace Seite 97 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] Für die nachfolgenden Statements wurden nach einander die Statistikwerte einzelner Tabellen gelöscht. ohne Statistikwerte für die Tabelle ADR_NAME und deren Indizes SQL > select * from , where and and adr_adressen adr_name adr_name.name like 'Meyer%' adr_adressen.name_id = adr_name.id rownum < 11; Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Cost=3201 Optimizer=CHOOSE 1 0 COUNT (STOPKEY) 2 1 HASH JOIN 3 2 TABLE ACCESS (BY INDEX ROWID) OF 'ADR_NAME' 4 3 INDEX (RANGE SCAN) OF 'ADR_NAME_IDX' 5 2 TABLE ACCESS (FULL) OF 'ADR_ADRESSEN' Statistics ---------------------------------------------------------0 recursive calls 4 db block gets 89 consistent gets ohne Statistikwerte für die Tabelle ADR_Adressen und deren Indizes SQL > select * from , where and and adr_adressen adr_name adr_name.name like 'Meyer%' adr_adressen.name_id = adr_name.id rownum < 11; Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Cost=3024 Optimizer=CHOOSE 1 0 COUNT (STOPKEY) 2 1 HASH JOIN 3 2 TABLE ACCESS (BY INDEX ROWID) OF 'ADR_NAME' 4 3 INDEX (RANGE SCAN) OF 'ADR_NAME_IDX' 5 2 TABLE ACCESS (FULL) OF 'ADR_ADRESSEN' Statistics ---------------------------------------------------------0 recursive calls 4 db block gets 89 consistent gets ______________________________________________________________________________________________________ Oracle Trace Seite 98 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Für das letzte Statement wurden alle Statistiken für die Tabellen und Indizes entfernt. Wenn nun die Annahme stimmt, zeigt sich der Zugriff exakt gleich, wie er für den Einsatz des Hint RULE zu erkennen war. Entfernen aller Statistikwerte auf Tabellen und Indizes SQL > select * from , where and and adr_adressen adr_name adr_name.name like '%Meyer%' adr_adressen.name_id = adr_name.id rownum < 11; Execution Plan ---------------------------------------------------------0 SELECT STATEMENT Optimizer=CHOOSE 1 0 COUNT (STOPKEY) 2 1 NESTED LOOPS 3 2 TABLE ACCESS (FULL) OF 'ADR_ADRESSEN' 4 2 TABLE ACCESS (BY INDEX ROWID) OF 'ADR_NAME' 5 4 INDEX (UNIQUE SCAN) OF 'ADR_NAME_ID_IDX' Statistics ---------------------------------------------------------0 recursive calls 4 db block gets 20844 consistent gets Tatsächlich ist der Ausführungsplan identisch dem RULE Zugriff und die COSTen werden nicht ausgewiesen. Auch die Anzahl CONSISTENT GETS sind identisch. Wenn auch im gezeigten Beispiel mit Teilstatistiken gute Ergebnisse erzielt wurden, ist davon auszugehen, daß es auch gegenteilige Ergebnisse gibt. 6.17. Treibende Tabelle Als treibende Tabelle wird die Tabelle in einem Join bezeichnet, welche die ersten Ergebnisse liefert, um eine Weiterverarbeitung vornehmen zu können. Am einfachsten ist dies zu erkennen für einen RULE Zugriff. Denn laut Definition benutzt Oracle die Tabelle als treibend, welche in der FROM Klausel als letztes eingetragen ist. ****************************************************************************** SELECT FROM , , /*+ RULE */ plz.plz, ort.ort ADR_ADRESSEN adr ADR_NAME name ADR_Ort ort ______________________________________________________________________________________________________ Oracle Trace Seite 99 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] , WHERE AND AND AND ADR_plz plz name.ID = plz.ID = ort.ID = name.name = call count ------- -----Parse 1 Execute 1 Fetch 595 ------- -----total 597 adr.NAME_ID adr.PLZ_ID adr.ORT_ID 'Meier' cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.01 0 0 0 0.00 0.00 0 0 0 127.60 165.63 11389 7483264 4 -------- ---------- ---------- ---------- ---------127.60 165.64 11389 7483264 4 rows ---------0 0 1784 ---------1784 Misses in library cache during parse: 1 Optimizer goal: RULE Parsing user id: 21 (LAMBERTZ) Rows ------0 1784 1493661 1493661 1493661 2987320 2987320 2987320 2987320 1784 2987320 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: HINT: RULE NESTED LOOPS NESTED LOOPS NESTED LOOPS TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ADRESSEN' TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_PLZ' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_PLZ_ID_IDX' (UNIQUE) TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_ORT' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_ORT_ID_IDX' (UNIQUE) TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_NAME' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_NAME_ID_IDX' (UNIQUE) ****************************************************************************** Wie unsinnig die Anordung der FROM Klausel für dieses Statement war, zeigt eine weitere TKProf Ausgabe für das gleiche Statement mit umgestellter FROM Klausel. Besser ist die Tabelle ADR_NAME als Führende zu bestimmen. ****************************************************************************** SELECT FROM , , , WHERE AND AND AND /*+ RULE */ plz.plz, ort.ort ADR_ADRESSEN adr ADR_Ort ort ADR_plz plz ADR_NAME name name.name = 'Meier' name.ID = adr.NAME_ID plz.ID = adr.PLZ_ID ort.ID = adr.ORT_ID call count ------- -----Parse 1 Execute 1 Fetch 595 ------- -----total 597 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 0.00 0.00 0 0 0 51.39 64.80 10056 3007636 4 -------- ---------- ---------- ---------- ---------51.39 64.80 10056 3007636 4 rows ---------0 0 1784 ---------1784 ______________________________________________________________________________________________________ Oracle Trace Seite 100 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Misses in library cache during parse: 1 Optimizer goal: RULE Parsing user id: 21 (LAMBERTZ) Rows ------0 1784 1785 1785 1493661 1495444 2987320 3568 3568 1784 3568 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: HINT: RULE NESTED LOOPS NESTED LOOPS NESTED LOOPS TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ADRESSEN' TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_NAME' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_NAME_ID_IDX' (UNIQUE) TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_PLZ' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_PLZ_ID_IDX' (UNIQUE) TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_ORT' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_ORT_ID_IDX' (UNIQUE) ****************************************************************************** Somit ist auch die Anzahl Rows, die bearbeitet wurden, wesenlich geringer als im Beispiel zu Beginn. Die treibende Tabelle sollte in jedem Fall jene sein, welche die beste Einschränkung in der WHERE Klausel enthält. Im CHOOSE Mode löst Oracle die Frage nach der treibenden Tabelle selbst, wie der folgende FIRST_ROWS Zugriff zeigt. ****************************************************************************** SELECT FROM , , , WHERE AND AND AND /*+ FIRST_ROWS */ plz.plz, ort.ort ADR_ADRESSEN adr ADR_NAME name ADR_Ort ort ADR_plz plz name.ID = adr.NAME_ID plz.ID = adr.PLZ_ID ort.ID = adr.ORT_ID name.name = 'Meier' call count ------- -----Parse 1 Execute 1 Fetch 595 ------- -----total 597 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.01 0.01 0 0 0 0.00 0.00 0 0 0 51.60 77.51 14084 3007636 4 -------- ---------- ---------- ---------- ---------51.61 77.52 14084 3007636 4 rows ---------0 0 1784 ---------1784 Misses in library cache during parse: 1 Optimizer goal: FIRST_ROWS Parsing user id: 21 (LAMBERTZ) Rows ------0 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: HINT: FIRST_ROWS ______________________________________________________________________________________________________ Oracle Trace Seite 101 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 1784 1785 1785 1493661 1495444 2987320 3568 3568 1784 3568 NESTED LOOPS NESTED LOOPS NESTED LOOPS TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ADRESSEN' TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_NAME' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_NAME_ID_IDX' (UNIQUE) TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_ORT' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_ORT_ID_IDX' (UNIQUE) TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_PLZ' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_PLZ_ID_IDX' (UNIQUE) ****************************************************************************** Nun gilt noch zu klären, warum aus der Tabelle ADR_NAME 1495444 Rows gelesen werden, obwohl eine solche Anzahl nicht in dieser Tabelle enthalten ist. SQL > SELECT table_name, num_rows FROM sys.dba_tables WHERE owner = 'LAMBERTZ' AND Table_Name IN( 'ADR_ADRESSEN', 'ADR_ANREDE', 'ADR_NAME', 'ADR_PLZ' ); TABLE_NAME NUM_ROWS ------------------------------ ---------ADR_ADRESSEN 1493217 ADR_ANREDE 4 ADR_NAME 280073 ADR_PLZ 584 3 rows selected. real: 230 SQL > Grund ist, daß eigentlich die Tabelle ADR_ADRESSEN als treibende eingesetzt wurde. Das liegt am NESTED LOOP. Hier der Beweis. Zu Beginn werden für die Tabelle ADR_ADRESSEN 1493661 Rows im FULL TABLE ACCESS ermittelt. Jede dieser Row‘s wird auf die Gleichheit name.id = adr.name_id gegen die Tabelle ADR_NAME geprüft dadurch, daß zwei Indexeinträge (siehe Indexe) aus dem Index ADR_NAME_ID_IDX zu einer Ergebniszeile der Tabelle führt. In Summe sind das 2987320 Row‘s aus dem Index. Eine Row der Tabelle wird ohne Indexzugriff gelesen. Dieses Vorgehen ist insofern besser weil weniger Datensätze bewertet werden müssen, wenn jeder Satz der Tabelle ADR_ADRESSEN gegen die indizierte Tabelle ADR_NAME verglichen wird und nicht umgekehrt. Trotz der kleinen Unschärfe kann nach wie vor davon ausgegangen werden, daß ADR_NAME die treibende Tabelle ist. ______________________________________________________________________________________________________ Oracle Trace Seite 102 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Für einen CHOOSE Zugriff ohne Einschränkung auf eine der gejointen Tabellen fällt die Auswahl als treibende Tabelle immer auf die mit der geringsten Anzahl Datensätze. Das liegt daran, daß Oracle mit jedem Satz aus dieser Tabelle die Ergebnisse der nächsten Tabelle bestimmt. Somit ist das Datenvolumen was sich für die Weiterverarbeitung ergibt am geringsten. ______________________________________________________________________________________________________ Oracle Trace Seite 103 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 6.18. Der Shared Pool Memory Der Shared Pool ist ein Teil der SGA. Die Größe des Shared Pool kann durch den Parameter SHARED_POOL_SIZE in der initSID.ora beeinflußt werden. Jedoch nicht einzelne Teile innerhalb des Shared Pool. Shared Pool Library Cache Dictionary Cache Shared SQL Area Row cache Library, API, ... Trigger, Functions, Procedures, Packages Control Stuctures Locks, Librarys, Cache handles, LRU, MRU, .... Reuseable Runtime Memory Sorts, ... Control Structures - Character Set conversion Memory - Network Securety Attributes Die einzelnen Größen (ohne die Control Strukturen)können ermittelt werden mit dem SELECT: SELECT NAME SUM( BYTES ) FROM SYS.V_$SGASTAT WHERE name IN( 'sql area', 'library cache', 'free memory', 'dictionary cache', 'PL/SQL DIANA', 'PL/SQL MPCODE' ) GROUP BY name ______________________________________________________________________________________________________ Oracle Trace Seite 104 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] 6.19. Die SQL Area Die SQL Area ist ein Speicherbereich in der SGA (system global area), der eine Historie genutzter Statements oder PL/SQL Routinen enthält, wobei eine Trennung für SQL- und PL/SQL Statements gegeben ist. In diesem SHARED MEMORY POOL landen alle PL/SQL Routinen sowie SELECT, INSERT, UPDATE und DELETE Anweisungen, wenn sie sich von denen unterscheiden, die schon im Pool vorliegen. Für viele Zugriffe auf die Datenbank ist die Zeit für das eigentliche UPDATE, INSERT, SELECT oder DELETE der Daten der geringere Anteil verglichen mit den Zeiten, die für das Kontrollieren der Statements und bestimmen des Zugriffspfad benötigt werden, wenn die CPU Leistung begrenzt verfügbar ist. Genau an dieser Stelle wird der SHARED MEMORY POOL genutzt, indem auf gespeicherte Informationen zu einem identischen Statement zurück gegriffen wird. Für die Erkennung, ob es sich bei dem Statement um eins handelt, das schon einmal verwendet wurde, findet ein 1:1 Vergleich der Statements statt. D.h. SELECT * FROM EMP ist nicht gleich SELECT * FROM Emp. Nur wenn kein Unterschied in der Schreibweise zu einem zuvor ausgeführten Statement besteht, wird der Parse Vorgang vorzeitig beendet und auf die Informationen des Shared Memory Pool verwiesen. Erkennbar ist dies, wenn z.B. ein SELECT Statement zweimal hintereinander in SQL*Plus ausgeführt wird. Der zweite Lauf liefert die Ergebnismenge schneller als der Erste. Auch der Ausführungsplan für den Aufruf einer View wird im Shared Memory Pool abgelegt und dem Statement mit der View zugeordnet - nicht dem Statement, was sich hinter der View verbirgt! Vorgehalten werden die Informationen im Shared Memory Pool nach dem FIFO Prinzip. Also das Statement, welches als erstes in den Shared Memory eingelagert wurde, wird als erstes wieder ausgelagert. 6.20. Library cache Der Library Chache ist ein Speicherbereich in der SGA, in dem die ausgeführten Packages, Funktionen, Proceduren oder Trigger vorgehalten werden. Der gesamte Quellcode eines solchen PL/SQL Moduls muß wenigstens einmal zur Ausführung gelangt sein. Der Library Cache wird ähnlich verwaltet wie die SQL Area und entfernt Module wieder aus diesem Speicher wenn sie über einen längeren Zeitraum nicht genutzt wurden und der vorhandene Library Chache nicht ausreicht, um ein neues Modul aufzunehmen. ______________________________________________________________________________________________________ Oracle Trace Seite 105 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 6.21. db file scattered read Dieses Wait Event zeigt an, daß für den User Process Daten in die SGA gelesen werden sollen aber für die Lesezugriffe das Device nicht schnell genug die Daten liefert. Nur für einen Full Table Acces oder Full Index Access entstehen diese Art Wartezustände, weil soviel Datenblöcke am Stück angefordert werden, wie durch initSID.ora Parameter „DB_FILE_MULTIBLOCK_READ_COUNT“ beschrieben sind und weil die Daten verteilt über das gesamte Device gelesen werden müssen, entsteht ein Wait Event. Dies hat zur Folge, daß kein durchgängiger Speicherbereich in der SGA belegt werden kann, sondern die Daten fragmentiert werden. 6.22. db file sequential read Dieses Wait Event zeigt an, daß für den User Process Daten in die SGA gelesen werden sollen, die Lesezugriffe des Device nicht schnell genug erfolgen und somit die zusammenhängende Lieferung von Daten nicht möglich ist. Der Unterschied zum „db file scattered read“ liegt darin, daß die gelesenen Daten zusammenhängend in dem Speicher abgelegt werden. Das liegt nicht zuletzt daran, daß nur ein Datenblock angefordert wurde. Und einen Block zusammenhängend in den Speicher zu übertragen ist nicht wirklich schwierig. Single Block I/O findet gewöhnlich nur für den Einsatz von Indexen statt. 6.23. Buffer busy waits Dieser Wartezustand entsteht, wenn ein Datenblock im Buffer cache von mehreren Prozessen beansprucht wird. Ein solcher Zustand wird auch „Block contention“ genannt. „buffer busy waits“ entstehen für einen DATA BLOCK, SEGMENT HEADER, UNDO HEADER und UNDO BLOCK. Welcher Typ welche Wartezustände verursacht kann ermittelt werden mit folgendem Statement: SELECT class, count FROM V$WAITSTAT WHERE count > 0 ORDER BY count DESC; SEGMENT HEADER Waits entstehen meist für eine nicht ausreichende Anzahl FREELISTs der Tabelle oder eines Indexes. Automatisches Segment Speicher Management, wie es für einen LOCALLY MANAGED TABLESPACE üblich ist, verhindern diese Probleme. DATA BLOCK Waits haben als Ursache: Unselektive Index Zugriffe ______________________________________________________________________________________________________ Oracle Trace Seite 106 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] Wenn mehere Prozesse den selben Block eines Indexes oder einer Datentabelle mit neuen Daten versorgen wollen. Letzlich wartet der Prozess auf die Zuweisung einer Speichersperre (Latch). UNDO HEADER ist eine Wartesituation auf den SEGMENT HEADER eines Rollbacksegment. Wir nicht das automatische Speichermanagement für die Rollback Segmente genutzt, sollte explizit mehr Rollback Segment zugeordnet werden. UNDO BLOCK ist die Wartesituation auf einen Block in einem Rollbacksegment. Wir nicht das automatische Speichermanagement für die Rollback Segmente genutzt, sollte explizit mehr Rollback Segment zugeordnet werden. 6.24. Free buffer waits Wenn der Server Prozess keinen Platz im Speicher findet, um Daten zu plazieren, wird der DataBaseWriter aufgefordert den Speicher freizuräumen durch schreiben aller „dirty blocks“ auf das Device. Erst wenn der Schreibvorgang abgeschlossen ist, werden die „dirty blocks“ aus dem Speicher gelöscht und stehen der Anfrage des Server Prozess bereit. Der DBWR ist langsam wenn: Das Device zu langsam ist und die Schreibzugriffe nicht schnell genug leisten kann Der Buffer Cache zu klein inst und der DBWR nur mit dem schreiben von „dirty blocks“ beschäftigt ist. Diese Situation wäre aber auch an einem zu geringen „Buffer cache hit ratio“ Wert zu erkennen. Der Buffer Cache zu groß ist und ein einziger DBWR Prozess nicht schnell genug schreiben von „dirty blocks“ bewerkstelligen kann. 6.25. Latch free Wenn der Server Prozess nicht direkt eine Speichersperre bewirken kann, sondern eine wiederholte Latch Anforderung starten muß, entsteht ein Wait. Mit hilfe eines Staments können solche Waits ausfindig gemacht werden: SELECT n.name, SUM(w.p3) Sleeps FROM V$SESSION_WAIT w, V$LATCHNAME n WHERE w.event = 'latch free' AND w.p2 = n.latch# GROUP BY n.name; ______________________________________________________________________________________________________ Oracle Trace Seite 107 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] 6.26. Log buffer space Dieser Wartezustand entsteht, wenn der Serverprozess auf freien Speicher im Log Buffer wartet. Wenn der LOG BUFFER schneller beschrieben wird, als der LGWR diese Information auf das Device schreiben kann. Wenn für das I/O kein bottleneck besteht, hilft nur das Vergrößern des LOG BUFFER 6.27. Log file switch Wenn der LGWR nicht schnell genug auf das umzuschalten kann, entsteht dieser Event. Dies Anzahl LogWriter Prozesse nicht ausreicht das bearbeiten. Aber auch zu kleine REDO LOG Wartezuständen. nächste Online Redo Log ist z.B. der Fall, wenn die Datenvolumen zeitnah zu FILES führen zu solchen 6.28. Parse Jedes Statement, das in irgendeiner Form zur Ausführung gelagen soll, muß geparst werden. Der Parser trifft alle Vorbereitungen (ähnlich dem Parser einer Programmiersprache), um das Statement ausführen zu können. In den Fällen, in denen die Parse Zeiten wesentlich mehr Zeit in Anspruch nehmen als die eigentliche Verarbeitung, kann die Möglichkeit des Pinnens in Anspruch genommen werden. Es können Packages, Proceduren oder Funktionen und einzelne Cursor gepinnt werden. Gerade dann, wenn die CPU Leistung relativ gering ausfällt. Aber pinnen darf nur kontrolliert erfolgen, weil jedes gepinnte Element den Speicher der SGA dauerhaft belegt. Statements sollten Bindevariablen beinhalten, weil sonst jedes Statement in der SGA gespeichert wird; auch wenn sich die Statements nur für eine Zahl in der WHERE Klausel unterscheiden! Im folgenden Beispiel erfolgt die wiederholte Ausführung des selben Statements. SQL > select * from ADR_ANREDE; 1 Firma 2 Frau 3 Herrn SQL > / 1 Firma 2 Frau 3 Herrn SQL > / 1 Firma ______________________________________________________________________________________________________ Oracle Trace Seite 108 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] 2 Frau 3 Herrn SQL > ****************************************************************************** select * from ADR_ANREDE call count ------- -----Parse 3 Execute 3 Fetch 6 ------- -----total 12 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.01 0.02 0 0 0 0.00 0.00 0 0 0 0.00 0.06 2 6 12 -------- ---------- ---------- ---------- ---------0.01 0.08 2 6 12 rows ---------0 0 9 ---------9 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) Rows Execution Plan ------- --------------------------------------------------0 SELECT STATEMENT GOAL: CHOOSE 3 TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ANREDE' ****************************************************************************** Die ausgewiesene Anzahl Parsings besteht aus einem vollständigen Parse und zwei parsings, die der Wiedererkennung vorzeitig beendet wurden. Das gleiche Statement mit veränderter Schreibweise benötigt keinen Disk Zugriff. Somit konnten die Daten aus dem DB_BUFFER bezogen werden, obwohl eine andere Schreibweise für das gleiche Statement vorlag! ****************************************************************************** select * from adr_anrede call count ------- -----Parse 1 Execute 2 Fetch 4 ------- -----total 7 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.01 0 0 0 0.00 0.00 0 0 0 0.01 0.02 0 4 8 -------- ---------- ---------- ---------- ---------0.01 0.03 0 4 8 rows ---------0 0 8 ---------8 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) Rows ------0 4 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: CHOOSE TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ANREDE' ****************************************************************************** ______________________________________________________________________________________________________ Oracle Trace Seite 109 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] Es wird deutlich, daß Oracle das Statement noch einmal aufbereitet (Typisches Beispiel ist, daß IN Angaben in eine OR Kette ersetzt). Damit liegt eine Wiedererkennung vor für das Statement, das schon im LIBRARY CACHE vorhanden und einem EXPLAIN PLAN zuordnet ist, auch mit veränderter Schreibweise. Hiermit ist erklärbar, warum keine Disk I/O entstand und die Daten für dieses Statement aus dem DB_BUFFER entnommen wurden! Deutlicher ist die Aufbereitung eines Statements durch Oracle zu erkennen in einem PL/SQL Block. Der nachfolgende Block wird zweimal ausgeführt und unterscheidet sich nur in der Schreibweise. begin for i in( select anrede from adr_anrede )loop insert into dual values ( '1' ); end loop; end; Der zugehörige TKProf Auszug zeigt, daß ****************************************************************************** SELECT ANREDE FROM ADR_ANREDE call count ------- -----Parse 2 Execute 2 Fetch 6 ------- -----total 10 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.07 0 6 0 0.00 0.00 0 0 0 0.00 0.04 2 6 9 -------- ---------- ---------- ---------- ---------0.00 0.11 2 12 9 rows ---------0 0 6 ---------6 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) (recursive depth: 1) Rows ------0 1 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: CHOOSE TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ANREDE' ****************************************************************************** begin for i in( select anrede from adr_anrede )loop insert into dual values ( '1' ); end loop; end; call count cpu elapsed disk query current rows ______________________________________________________________________________________________________ Oracle Trace Seite 110 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] ------- -----Parse 1 Execute 1 Fetch 0 ------- -----total 2 -------- ---------- ---------- ---------- ---------0.01 0.00 0 0 0 0.00 0.01 0 0 0 0.00 0.00 0 0 0 -------- ---------- ---------- ---------- ---------0.01 0.01 0 0 0 ---------0 1 0 ---------1 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) ****************************************************************************** INSERT INTO DUAL VALUES ( '1' ) call count ------- -----Parse 2 Execute 6 Fetch 0 ------- -----total 8 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 0.00 0.00 0 2 13 0.00 0.00 0 0 0 -------- ---------- ---------- ---------- ---------0.00 0.00 0 2 13 rows ---------0 6 0 ---------6 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) (recursive depth: 1) Rows ------0 Execution Plan --------------------------------------------------INSERT STATEMENT GOAL: CHOOSE ****************************************************************************** begin for i in( SELECT anrede FROM adr_anrede )loop INSERT into dual values ( '1' ); end loop; end; call count ------- -----Parse 1 Execute 1 Fetch 0 ------- -----total 2 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 0.00 0.01 0 0 0 0.00 0.00 0 0 0 -------- ---------- ---------- ---------- ---------0.00 0.01 0 0 0 rows ---------0 1 0 ---------1 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) ****************************************************************************** Das INSERT und auch das SELECT Statements wurde in Großbuchstaben innerhalb des Cursors ausgeführt und dadurch wiedererkannt. Für das SELECT Statement wurde die Anzahl Rows im Ausführungsplan aus der Tabelle ADR_ADRESSEN mit einer Row ausgewiesen. Das ist für den FULL TABLE ACCESS auf die Tabelle mit drei Datensätzen falsch. Das zeigt auch der TKProf für die Ausführung des Statements direkt in SQL*Plus. ****************************************************************************** ______________________________________________________________________________________________________ Oracle Trace Seite 111 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] SELECT ANREDE from ADR_ANREDE call count ------- -----Parse 1 Execute 1 Fetch 2 ------- -----total 4 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 0.00 0.00 0 0 0 0.00 0.00 0 2 4 -------- ---------- ---------- ---------- ---------0.00 0.00 0 2 4 rows ---------0 0 3 ---------3 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) Rows ------0 3 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: CHOOSE TABLE ACCESS GOAL: ANALYZED (FULL) OF 'ADR_ANREDE' ****************************************************************************** Das folgende Problem zeigt, das Statements innerhalb eines Cursores (LOOP) nur einmalig geparsed werden!! ****************************************************************************** declare v_count NUMBER( 7 ); begin SELECT count(*) INTO v_count FROM adr_anrede; SELECT count(*) INTO v_count FROM adr_anrede; SELECT count(*) INTO v_count FROM adr_anrede; for cur_rec in( SELECT plz_id FROM adr_adressen where rownum < 11 )loop SELECT count(*) INTO v_count FROM adr_anrede; end loop; end; call count ------- -----Parse 1 Execute 1 Fetch 0 ------- -----total 2 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.02 0.02 0 0 0 0.01 0.00 0 0 0 0.00 0.00 0 0 0 -------- ---------- ---------- ---------- ---------0.03 0.02 0 0 0 rows ---------0 1 0 ---------1 Misses in library cache during parse: 1 Optimizer goal: CHOOSE Parsing user id: 21 (LAMBERTZ) ****************************************************************************** SELECT COUNT(*) FROM ADR_ANREDE call count ------- -----Parse 4 Execute 13 Fetch 13 ------- ------ cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.03 0.08 0 0 0 0.00 0.00 0 0 0 0.00 0.04 2 13 52 -------- ---------- ---------- ---------- ---------- rows ---------0 0 13 ---------- ______________________________________________________________________________________________________ Oracle Trace Seite 112 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] total 30 0.03 0.12 2 13 52 13 ****************************************************************************** SELECT PLZ_ID FROM ADR_ADRESSEN call count ------- -----Parse 1 Execute 1 Fetch 11 ------- -----total 13 WHERE ROWNUM < 11 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.01 0 0 0 0.00 0.00 0 0 0 0.00 0.00 0 10 4 -------- ---------- ---------- ---------- ---------0.00 0.01 0 10 4 rows ---------0 0 10 ---------10 ****************************************************************************** 6.28.1. Hard Parse 1. Sind alle Signalworte wie FROM oder ORDER BY in der richtigen Reihenfolge? Fehlen Signalworte oder sind welche zu viel ? Im Fehlerfall wird der Parse Vorgang abgebrochen und eine Meldung ausgegeben. Z. B. führt SELECT FROM TABELLE zu einem Fehler, weil kein Attribut aus der Tabelle angegeben wurde, das selektiert werden soll. 2. Kontrollieren, ob dieses Statement bereits in der SQL Area vorliegt und nicht als invalid markiert ist. Verglichen wird der eigentliche Statement Text bis auf das letzte Bit. Zu beachten ist die Groß- und Kleinschreibung, Spaces und Zeilenumbrüche. Wenn also ein exakt gleiches Statement gefunden wurde, wird der Parse Vorgang mit dem Bearbeiten von Bindevariablen beendet und das Statement ausgeführt. Alle erforderlichen Informationen werden dann aus dem Shared Memory Pool gelesen. Wird das Statement nicht in der SQL Area gefunden, handelt es sich um ein neues Statement und wird in die SQL Area übernommen. 3. Optimieren des Statements. Z.B. wird: SELECT * FROM DEPT WHERE DEPT.EMPNO IN (SELECT EMPNO FROM EMP) optimiert in SELECT DEPT.* FROM DEPT, EMP WHERE DEPT.EMPNO = EMP.EMPNO ______________________________________________________________________________________________________ Oracle Trace Seite 113 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] Hier ein weiteres Beispiel, wie IN(...) in eine OR Kette zerlegt wird. Deutlich in der Trace Datei: ****************************************************************************** select /*+ RULE */ * from adr_plz where id=10 or id=11 or id=12 call count ------- -----Parse 1 Execute 1 Fetch 2 ------- -----total 4 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 0.00 0.00 0 0 0 0.00 0.00 0 9 0 -------- ---------- ---------- ---------- ---------0.00 0.00 0 9 0 rows ---------0 0 3 ---------3 Misses in library cache during parse: 1 Optimizer goal: RULE Parsing user id: 21 (LAMBERTZ) Rows ------0 3 1 2 1 2 1 2 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: HINT: RULE CONCATENATION TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_PLZ' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_PLZ_ID_IDX' (UNIQUE) TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_PLZ' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_PLZ_ID_IDX' (UNIQUE) TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_PLZ' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_PLZ_ID_IDX' (UNIQUE) ****************************************************************************** select /*+ RULE */ * from adr_plz where id IN( 10,11,12) call count ------- -----Parse 1 Execute 1 Fetch 2 ------- -----total 4 cpu elapsed disk query current -------- ---------- ---------- ---------- ---------0.00 0.00 0 0 0 0.00 0.00 0 0 0 0.00 0.00 0 9 0 -------- ---------- ---------- ---------- ---------0.00 0.00 0 9 0 rows ---------0 0 3 ---------3 Misses in library cache during parse: 1 Optimizer goal: RULE Parsing user id: 21 (LAMBERTZ) Rows ------0 3 1 2 1 Execution Plan --------------------------------------------------SELECT STATEMENT GOAL: HINT: RULE CONCATENATION TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_PLZ' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_PLZ_ID_IDX' (UNIQUE) TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_PLZ' ______________________________________________________________________________________________________ Oracle Trace Seite 114 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] 2 1 2 INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_PLZ_ID_IDX' (UNIQUE) TABLE ACCESS GOAL: ANALYZED (BY INDEX ROWID) OF 'ADR_PLZ' INDEX GOAL: ANALYZED (UNIQUE SCAN) OF 'ADR_PLZ_ID_IDX' (UNIQUE) ****************************************************************************** Für beide Statements wird der gleiche Explain Plan ausgewiesen. 5. Einfügen des Object Owner in die SQL Anweisung, wenn erforderlich. So wird aus: SELECT * FROM DEPT das Statement wie folgt: SELECT * FROM SCOTT.DEPT 6. Existiert die angegebene Tabelle im Data Dictionary und liegen Zugriffsrechte für den Gebrauch dieser Tabelle, Modul oder Synonym vor? 7. Festlegen der Zugriffsart, wenn die Datenbank im CHOOSE Modus arbeitet. Die CHOOSE Einstellung hat einen COST-basierten Zugriff zur Folge, wenn Analyse Ergebnisse für die Tabellen vorliegen. Ist dies nicht der Fall, wird der RULE (regelbasierte) Zugriff gewählt. 8. Wurde die CHOOSE gewählt, erfolgt eine Überprüfung aller beteiligter Objekte, ob für wenigstens eins dieser Objekte Statistikwerte vorliegen. Ist dies der Fall, optimiert Oracle das Statement entsprechend der Regeln, wie sie z.B. für einen RULE Zugriff beachtet werden müssen. 9. Erstellen des Ausführungsplans für dieses Statement, dem EXPLAIN PLAN und speichern im Shared Memory Pool (mit dem EXPLAIN PLAN wird festgelegt mit welcher Vorgehensweise der Zugriff auf die Daten erfolgen wird. Für UPDATE, DELETE oder SELECT wichtig. Der EXPLAIN PLAN enthält, welche Indizes genutzt, welche Tabellen im FULL TABLE ACCESS gelesen werden und welche der gejointen Tabellen die Führungstabelle sein wird). Der EXPLAIN PLAN wird dem ursprünglichen Statement zugeordnet und im Shared Memory Pool verwaltet. 10. Bearbeiten der Bindevariablen 11. Ausführen des Statement in der Datenbank. 12. Liefern der Ergebnismenge ______________________________________________________________________________________________________ Oracle Trace Seite 115 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, [email protected] Hard Parsings sollten immer in einem guten Verhältnus zueinander stehen, nämlich zu einer erhöhten Anzahl „execute count“ möglichst wenig „parse count“. Mit folgendem Statement kann Klarheit geschaffen werden: SELECT FROM WHERE AND AND ORDER ss.sid, sn.name, ss.value V$SESSTAT ss, V$STATNAME sn name IN ('parse count (hard)','execute count') ss.statistic# = sn.statistic# ss.value > 0 BY sid, value 6.28.2. Soft Parse Ein Softparse steht in direkter Verbindung mit einem RELOAD. Wenn nämlich der LIBRARY CACHE nicht ausreicht, erfolgt ein „aged out“. D.h., daß die Infomationen (z.B. Explain plan, Zugriffsrechte, usw.) aus dem LIBRARY CACHE entfernt werden und ausschließlich das Statement in der SQL Area gehalten wird. Erfolgt nun die wiederholte Anforderung, das Statement auszuführen, ist ein „soft parse“ Vorgang erforderlich. D.h., nur die Syntax ist geprüft und alles weitere muß erneut bestimmt werden. Der Einstieg in den Parse Prozess erfolgt an Position 2 der Hard Parse Beschreibung. Reloads können ermittelt werden mit folgendem Statement: SQL > SELECT SQL_TEXT, LOADS, FIRST_LOAD_TIME, PARSE_CALLS, DISK_READS, BUFFER_GETS, ROWS_PROCESSED, OPTIMIZER_MODE FROM V$SQLAREA WHERE LOADS > 1 ORDER BY first_load_time asc, buffer_gets desc, disk_reads desc; ______________________________________________________________________________________________________ Oracle Trace Seite 116 CL ambertz onsulting Rainer Lambertz, Kreuzstraße 54, 41564 Kaarst, eMail: [email protected] 7. Index A H AUTOTRACE ............................................. 10, 63 HIGH WATER MARK.......................................93 C P CHOOSE......................................................... 104 Count ................................................................ 23 CPU ................................................................... 23 Current ............................................................. 24 CURRENT ........................................................ 91 Parse..................................................................20 PLAN_TABLE....................................................63 PLUSTRACE ........................................................9 D Disk ................................................................... 23 DISTINCT_KEYS....................................... 94, 95 Q Query .................................................................23 R Rows ..................................................................25 E S Elapsed............................................................. 23 Execute............................................................. 20 SGA ............................................................91, 105 Shared Pool .....................................................105 SHARED_POOL_SIZE ..................................105 F Fetch ................................................................. 23 ______________________________________________________________________________________________________ Oracle Trace Seite 117