corner image
home ·Wie man Oracle SQL's optimiert


letzte Änderung:
TCP-States
Netzwerk
Links
Australien
Wertpapiere

Wie man Oracle SQL's optimiert

Claus Ebert

Version 0.01

Kurze sammlung von Methoden zum optimieren von Oracle (SQL's)

1.Zugriffsoptimierung

2.Analyse

3.Rule-Based-Optimizer

4.Cost-Base-Optimizer

5.Einsatz von DBMS_STATS


1. Zugriffsoptimierung

1.1 SQL*Plus (autotrace, timing)

Wie verwende ich SQL*Plus zur Zugriffsoptimierung

Eine schnelle möglichkeit Ausführungspläne zu einem SQL zu ermitteln ist der autotrace Parameter.
Dazu ist es notwendig, das man Zugriff auf die Tabelle EXPLAIN_PLAN hat.
Falls die Tabelle noch nicht existiert, hier steht wie man das macht.
Desweiteren muß die Rolle plustrace erzeugt worden sein (Script plustrace.sql im rdbms-Verzeichnis der Datenabank), und dem Benutzer zugewiesen sein.
Durch die Eingabe von :
SET AUTOTRACE ON
in SQL*Plus am Eingabeprompt wird die automatische Trace-Funktion aktiviert. Jeder jetzt eingegeben SQL-Befehl produziert nicht nur das Ergebnis der Abfrage, sondern auch den Zugriffsplan:

SQL> select PSN_ID,  PSN_NAME_KURZ from krn_person where psn_id=111111;

    PSN_ID PSN_NAME_KURZ
---------- ----------------------------------------
    111111 Ebert

Ausführungsplan
----------------------------------------------------------
   0      SELECT STATEMENT Optimizer=CHOOSE (Cost=2 Card=1 Bytes=48)
   1    0   TABLE ACCESS (BY INDEX ROWID) OF 'KRN_PERSON' (Cost=2 Card=1 Bytes=48)
   2    1     INDEX (UNIQUE SCAN) OF 'PSN_PK' (UNIQUE) (Cost=1 Card=1)

Statistiken
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          3  consistent gets
          0  physical reads
          0  redo size
        241  bytes sent via SQL*Net to client
        309  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

Der oben Dargestellte Plan zeigt, daß über den Primary Key PSN_PK auf die Tabelle KRN_PERSON im CHOOSE-Modus zugegriffen wird.
Es sind 3 Oracle-Blöcke im konsistenten Modus gelesen worden, und der Client hat 241 Bytes gesendet und 309 Bytes empfangen.

Durch eine genauere Parametisierung kann die Ausgabe eingeschränkt werden.
SET AUTOTRACE TRACE EXPLAIN
zeigt nur den Ausführungsplan. Alles andere wird unterdrückt.
SET AUTOTRACE TRACE STATISTICS
gibt nur die Zugriffsstatistiken aus.

Falls auch noch die Antwortzeiten gemessen werden sollen, kann das mit
SET TIMING ON
erreicht werden. (Das Ausgabeformat ist nicht auf allen Plattformen einheitlich)


top

1.2 EXPLAIN PLAN und die PLAN_TABLE

Eine weitere Möglichkeit den Ausführungsplan eines SQL-Statements zu ermitteln ist das EXPLAIN PLAN Kommando.

Die Tabelle PLAN_TABLE hat folgende Spalten:

Spalte Bedeutung
statement_id Eindeutiger Kenner der analysierten SQL-Statements
timestamp Zeitpunkt der Abfrage
remarks Freies Textfeld. Kann nach der Auswertung eingefügt werden
operation Art der durchgeführten Operation, z.B. table access . In ID=0 wird immer die Art des SQL-Befehls angegeben (Select ...)
options Genauere Angaben zu der durchgefühten Aktion
object_node Name des Datenbank-Links, der das betreffende Objekt referenziert
object_owner Eigentümer des Objekts/Schema, das das Objekt enthält
object_name Name des Objekt, auf das sich die Operation bezieht
object_instance Eine Nummer, die die Position des Objekts in dem SQL-Befehl wiedergibt. z.B: bei SELECT-Befehlen die Position in der from-Klausel
object_type Nähere Bescheibung des Objekt (z.B. UNIQUE für Indices)
optimizer Die aktuelle Einstellung des optimizers (z.B. COOSE, RULE...)
search_columns Nicht Benutzt
id Eindeutige und fortlaufende Nummer jeder Operation
parent_id ID die als nächtes folgt.
position Reigenfolge der Abarbeitung aller Operationen mit der gleichen statement_id und parent_id
other Zusätzliche Informationen bei verteilten Abfragen, z.B. SQL-Befehlsketten, die zu einer Fremddatenbank geschickt wurden.
other_tag Nähere Beschreibung der Daten in der other-Spalte. Wichtig bei parallelen Abfragen
cost "Kosten" der Operation (als ganze Zahl)
cardinality Anzahl der Zeilen, die für diese Operation erwartet werden.
bytes Anzah Bytes, die für diese Operation erwartet werden
partition_start Partition, bei der der Zugriff beginnt
partition_stop Partition, bei der der Zugriff endet
partition_id Operation, die den partitionierten Zugriff errechnet hat


Durch den Befehl:

EXPLAIN PLAN SET STATEMENT_ID = 'Q1' FOR
  SELECT PSN_ID,  PSN_NAME_KURZ FROM KRN_PERSON WHERE PSN_ID=111111;

Wird der Zufriffsplan der Datenbankabfrage in der PLAN_TABLE unter den Name 'Q1' protokilliert.
Es ist sehr sinnvoll den Ausführungsplänen eigene Namen zu geben, da so verschieden Pläne in der Tablelle gespeichert werden können.
Mit

SELECT LPAD(' ',2*(LEVEL-1))||OPERATION||' '||OPTIONS||' '||OBJECT_NAME||' '
       ||DECODE(OBJECT_NODE,'','','['||OBJECT_NODE||'] ')
       ||DECODE(OPTIMIZER,'','','['||OPTIMIZER||'] ')
       ||DECODE(ID,0,'Cost='||POSITION,
          DECODE(COST,'','',' Cost='||COST||' '||
            DECODE(ID,0,'','Card='||CARDINALITY||' ')
            ||DECODE(ID,0,'','Bytes='||BYTES)
            )
          ) QUERY
 FROM PLAN_TABLE
START WITH ID = 0 AND STATEMENT_ID = 'Q1'
CONNECT BY PRIOR ID = PARENT_ID AND STATEMENT_ID = 'Q1'
ORDER BY ID;

wird der Plan in einer hirarchischen Struktur ausgegben. Man kann natürlich auch das Script utlxpls.sql in Oracle/rdbms/admin Verzeichnis verwenden.
Wichtig bei diesen Statement sind die STATEMENT_ID='Q1' Klauseln, mit denen der auszugebende Plan benannt wird. Sie sollten angepasst werden.

top

1.3 TKPROF

Wem die vom Optimizer ausgegebenen Zufriffsoperationen zu wenig ist, der kann sich mit SQL_TRACE und TKPROF die tatsächlichen Werte anzeigen lassen.

Die Arbeit mit tkprof läßt sich in fünf unterschiedliche Phasen einteilen:

1. Vorbereitung der Instance.
2. Aktivieren von SQL_TRACE.
3. Starten der zu untersuchenden Anwendung.
4. Deaktivieren von SQL_TRACE.
5. Analysieren der Ergebnisse.

1.) Zur Vorbereitung der Instance sind die folgenden Parameter zu setzen (bzw. zu Prüfen):

  SELECT NAME, VALUE FROM V$PARAMETER;

zeigt alle Parameter, und deren momentane Werte an.

timed_statistics=true
Dieser Parameter schaltet die interne Zeitmessung ein. Es geht auch ohne, aber dann werden auch keine Ausführungszeiten angezeigt.
Da dieser Parameter das Laufzeitverhalten der Datenbank beeinflußt sollte er nur bei Bedarf eingeschaltet werden.
Ab Version 8 kann die Zeitmessung auch in der Session aktiviert werden.
  ALTER SESSION SET TIMED_STATISTICS=TRUE;

Der Parameter max_dump_file_size legt die maximale Größe der vom Trace produzierten Files fest (in Blöcken des Betriebssystems). Ist der maximale Wert erreicht, wird einfach nicht mehr weiter geschrieben und keine Warnungen oder Fehlermeldungen ausgegeben.
Die Ergebnisse werden in so einem Fall NICHT KORREKT sein.

Im Parameter user_dump_path befindet sich der Path, wo (auf dem Oracle-Server) die Trace-Files abgelegt werden.

2.) Das SQL_TRACE aktivieren ( innerhalb einer Session oder außerhalb einer Session ).
evtl die Zeitmessung einschalten
  ALTER SESSION SET TIMED_STATISTICS=TRUE;

3.) Ausführen der Anwendung. z.B: Ausführen eines SQL's;
  select * from krn_person where psn_id = 111111;

4.) Zu Deaktivieren von SQL_TRACE gehen wir genauso vor, wie beim Aktivieren, nur das das wir SQL_TRACE FALSE zuweise. z.B:
   ALTER SESSION SET SQL_TRACE=FALSE;

5.) Die erzeugte Datei muß zuerst im user_dump_path Verzeichnis gesucht werden (siehe oben). Sie hat normalerwise das Format "ora_nnnn.trc", wobei nnnn die Prozessnummer des Serverprozesses ist, zu dem der User Connected ist.
Jetzt kann die Trace-Datei mit dem Programm tkprof(windows: tkprof80) formatiert, der zugriffsplan generiert und in eine Ausgabedatei geschrieben werde.
  tkprof tracedatei ausgabedatei explain=user/passwort
Die Ausgabedate kann danach mit jedem Texteditor betrachtet werden.

z.B: unser obiger SQL

TKPROF: Release 8.1.5.0.0 - Production on Thu Oct 23 18:34:23 2003

(c) Copyright 1999 Oracle Corporation.  All rights reserved.
Trace file: ora_27286_ch.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
********************************************************************************

select * 
from
 krn_person where psn_id = 111111

call     count       cpu    elapsed       disk      query    current        rows
------- ------  -------- ---------- ---------- ---------- ----------  ----------
Parse        1      0.01       0.02          0          0          0           0
Execute      1      0.00       0.01          0          0          0           0
Fetch        2      0.00       0.00          0          6          0           1
------- ------  -------- ---------- ---------- ---------- ----------  ----------
total        4      0.01       0.03          0          6          0           1

Misses in library cache during parse: 1
Optimizer goal: CHOOSE
Parsing user id: 60  (CHOC)

Rows     Row Source Operation
-------  ---------------------------------------------------
      1  TABLE ACCESS BY INDEX ROWID KRN_PERSON 
      1   INDEX UNIQUE SCAN (object id 32071)

Rows     Execution Plan
-------  ---------------------------------------------------
      0  SELECT STATEMENT   GOAL: CHOOSE
      1   TABLE ACCESS   GOAL: ANALYZED (BY INDEX ROWID) OF 'KRN_PERSON'
      1    INDEX   GOAL: ANALYZED (UNIQUE SCAN) OF 'PSN_PK' (UNIQUE)

********************************************************************************

top

1.4 Full Table Scans finden

seit Version 9i könne wir auf einfache Weise alle Full Table Scans auslesen

Full Table Scans sind in normalen OLTP Datenbanken unerwünscht,
und mit Hilfe des folgenden Scripts können wir diesen SQL's auf die schliche kommen.
set pages 1000 
set feedback off
set lines 150
Column sql_text Format A50 Heading 'Full Table Scans' Wrap
break on sql_text skip 1
Select Executions, Sorts, Disk_Reads, Buffer_Gets, CPU_Time,
       Elapsed_Time, sql_text
  from v$sqlarea
 where (address, hash_value) in (Select address, hash_value from
v$sql_plan   where options   like '%FULL%' and operation like '%TABLE%') 
Order by Elapsed_Time
/
set feedback on 

top

2. Analyse

Scripts zum Analysieren der Datenbank

2.1 Dynamisches Analysieren

Dynamisches Analysieren aller Tabellen eines DB-Owners

Ich benutze diese Script, indem ich den ich das Statement
DEFINE OWN='PTS'
meinen Bedürfnissen anpasse:

CLEAR BUFFER
SET ECHO ON
SET FEED ON
SET TIMING ON
SET VERIFY OFF
SET SERVEROUTPUT ON  SIZE 1000000

DEFINE OWN='PTS'

DECLARE
  SQL_STMT    VARCHAR2(2000);
BEGIN
  FOR TAB_REC IN (SELECT OWNER,TABLE_NAME
      FROM ALL_TABLES WHERE OWNER = UPPER('&OWN') 
    )  LOOP
    SQL_STMT := 'BEGIN DBMS_STATS.GATHER_TABLE_STATS  (' ||
                'OWNNAME  => :1 , TABNAME => :2 , PARTNAME=> NULL, ' ||
                'ESTIMATE_PERCENT => 20, DEGREE => 5 , CASCADE => TRUE); END;'  ;
    DBMS_OUTPUT.PUT_LINE (SQL_STMT);
    EXECUTE IMMEDIATE SQL_STMT USING TAB_REC.OWNER, TAB_REC.TABLE_NAME ;
  END LOOP;
END;
/

top

3. Rule-Based-Optimizer

3.1 Funktionsweise des Rule-Based-Optimizers

Der regelbasierte Optimizer verwendet einen vordefiniterten Satz an Regeln um den Ausführungsplan zu ermitteln.

Unter folgenden Bedingungen wird der Rule-Based-Optimizer von der Datenbank verwendet:

  • In der INIT.ORA wird OPTIMIZER_MODE=RULE angegeben.
  • In der INIT.ORA wird OPTIMIZER_MODE=CHOOSE angegeben, aber es existieren keine Statistiken.
  • Die Session wird mit
    ALTER SESSION SET OPTIMIZER_MODE=RULE;
    umgestellt
  • Die Session wird mit
    ALTER SESSION SET OPTIMIZER_MODE=RULE;
    umgestellt, aber es sind keine Statistigen vorhanden.
  • Der Hint /*+ RULE */ wird in dem SQL verwendet.
top

3.2 Rangfolge

Rang Bedingung
1 ROWID=konstant
2 Cluster-Join mit einem Unique oder Primärschlüssel = konstant
3 Hash-Cluster-Schlüssel mit Unique oder Primärschlüssel = konstant
4 Gesamter zusammengesetzter Unique-Index = konstant
5 Eindeutige, indizierte Spalte = konstant
6 Gesamter Cluster-Schlüssle = entsprechender Cluster-Schlüssel einer anderen Tabelle im selben Cluster
7 Hash-Cluster-Schlüssel = konstant
8 Gesanter Cluster-Schlüssel = konstant
9 Gesamter Non-UNIQUE CONCATENATED-ndex = konstant
10 Non-UNIQUE-index-Merge
11 Gesamter zusammengesetzter Index = untere Grenze
12 Die führende(n) Spalte(n) des zusammengesetzten Index = konstant
13 Indizierte Spalte zwischen dem niedrigen und hohen Wert oder indizierte Spalte LIKE 'ABC%' (begrenzter Bereich)
14 Non-UNIQUE-indizierte Spalte zwischen dem niedrigen und hohem Wert oder r indizierte Spalte LIKE 'ABC%' (begrenzter Bereich)
15 UNIQUE-indizierte Spalte oder Konstante (nicht begrenzter Bereich)
16 Non-UNIQUE-indizierte Spalte oder Konstante (nicht begrenzter Bereich)
17 Gleichheit oder nicht-indiziert = Spalte oder Konstante (Sort/Merge-Join)
18 MAX oder MIN einer einzelnen indizierten Spalte
19 ORDER BY gesamter Index
20 Full-Table-Scans


top

4. Cost-Base-Optimizer

4.1

Kommt noch

top

5. Einsatz von DBMS_STATS

DBMS_STATS wurde erst mit Oracle 8i eingeführt und ermöglicht (unter anderem) das kopieren von Statistiken von einer Datenbank (Produktion) in eine andere (Test)

5.1 Kopieren von Statistiken

DBMS_STATS ermöglicht das Kopieren von Statistiken von einem Schema in ein anderes

Um die Statistiken eines Schemas zu speichern muss zuerst (falls noch nicht vorhanden) eine Tabelle angelegt werden:

EXEC SYS.DBMS_STATS.CREATE_STATS_TABLE('CEDB','CEDB_STATS');

wobei CEDB das Datenbank-Schema (oder DB-Owner) ist und CEDB_STATS Den Tabellennamen darstellt.

Als nächstes wird die neu angelegte Tabelle mit den Statistiken gefüllt:

EXEC SYS.DBMA_STATS.EXPORT_SCHEMA_STATS('CEDB','CEDB_STATS','UNIQUE_ID');

Der Wert UNIQUE_ID ist ein Textfeld, das mit einer eindeutigen Bezeichnung gefüllt sein sollte (Datum und Zeit bieten sich hier an)
Da in der Statistik-Tabelle mehrere Statistiken gespeichert werden können ist diese Bezeichnung notwendig um auf eine bestimmte Statistik zugreifen zu können (siehe auch Rückkehr zu vorherigen Statistiken)
Jetzt befinden sich die aktuellen Statistiken in der Tabelle CEDB_STATS
Falls man diese Statistiken in eine andere Datenbank, etwa aus der Produktion in die Entwicklungsdatenbank, kopieren möchte, exportiert und importiert man die Statistk-Tabelle:

exp cedb/geheim@prod file=stats.dmp tables=cedb_stats

imp cedb/geheim@test file=stats.dmp tables=cedb_stats

Nach dem Import (evtl muss vor dem Import die CEDB_STATS tabelle mit einem TRUNCATE geleert werden um Import-Fehler zu vermeiden) kann man die Statistiken mit einem

EXEC SYS.DBMS_STATS.IMPORT_SCHEMA_STATS('CEDB','CEDB_STATS','UNIQUE_ID');

in die Zieldatenbank eingefügen.

Danach sollte sich der Cost-Base-Optimizer in der Testdatenbank genauso wie in Produktion verhalten

top

5.2 Rückkehr zu vorherigen Statistiken

Viele DBA's analysieren ihre Datenbank in regelmäßigen Abständen.
Dagegen in prinzipiell nichts einzuwenden, da eine erneute Analyse bei veränderten Datenmengen normalerweise eine Verbesserung der Performance bewirkt.

Die Betonung liegt hier auf normalerweise, da in einigen Fällen der Cost-Base-Optimizer den völlig falsche Weg einschlägt.

Abhilfe schafft hier das DBMS_STATS Package, mit dem es möglich ist eine Historie der Statistik vorzuhalten.

Dazu erstellt man sich für das Schema die Statistik-Tabelle (falls noch nicht vorhanden), in der die Historie gespeichert wird.
EXEC SYS.DBMS_STATS.CREATE_STATS_TABLE('CEDB','CEDB_STATS');

Jetzt überträgt man die aktuellen Statistiken in die Statistik-Tabelle:
EXEC SYS.DBMS_STATS.EXPORT_SCHEMA_STATS('CEDB','CEDB_STATS','PROD_20051129');

Danach kann man die neuen Statistiken erstellen:
EXEC SYS.DBMS_STATS.GATHER_SCHEMA_STATS('CEDB',10,4,true));

Falls sich nun herausstellt, das die neuen Statistiken zu problemem führen kann man die vorher gesicherten Statstiken mit
EXEC SYS.DBMS_STATS.IMPORT_SCHEMA_STATS('CEDB','CEDB_STATS','PROD_20051129');

wiederzurückholen.

top
  ©2016· Claus Ebert · Emailemail senden