Auf seiner Suche nach dem optimalen Ausführungsplan ist der Optimizer in einen Käfig gesperrt, der von den SQL-Abfragen und dem physischen Datenbank-Design vorgegeben ist. Per Definition kann der Optimizer nur den besten Ausführungsplan innerhalb dieses Käfigs finden – auch wenn außerhalb bessere Ausführungspläne sind. In diesem Vortrag werden wir sehen, wie Indizierung diesen Käfig beeinflusst und lernen, wie man durch kluge Indizierung den Käfig so gestaltet, dass der absolut beste Ausführungsplan in der Reichweite des Optimizers liegt.
3. Des Suchraum ist beschränkt
“the query optimizer determines
the most efficient execution plan*”
…den effizientesten? Von welchen?
*http://docs.oracle.com/cd/E16655_01/server.121/e15857/pfgrf_perf_overview.htm#TGDBA94082
4. Des Suchraum ist beschränkt
Der Optimizer...
‣Berücksichtigt nur bestehende Indizes
➡ Andere Indizes könnten bessere Performance liefern
‣Versteht “verstümmelte” Abfragen nicht
➡ Einfachere Abfragen können Performance verbessern
‣Hat eingebaute Beschränkungen
➡ Theoretisch mögliche Ausführungspläne werden nicht
berücksichtigt
5. Den besten Plan in den Suchraum bringen
... it determines the most efficient
execution plan out of the remaining ones.
Damit der Optimizer den besten Plan finden
kann, müssen wir sicherstellen, dass dieser
Plan innerhalb seiner Grenzen liegt.
6.
7.
8. Zwei Schritte zum absolut besten Ausführungsplan:
1. Daten-Lokalität maximieren
‣ Der gute alte B-Tree Index ist das #1 Tool
‣ Partitionen sind überbewertet
‣ Tabellen-Cluster sind unterbewertet
Indizes und Queries müssen zusammenpassen
2. Abfrage optimal schreiben
‣ Explizite Bereichsbedingungen nutzen
‣ Top-N Abbrüche verwenden
‣ Index-Reihenfolge ausnutzen
Denke in
sortierten
Mengen
12. Indizes nutzen:
Spaltenreihenfolge definiert Daten-Lokalität
Faustregeln (in ~97% optimal):
‣ Konjunktive ist-gleich Suchen sind super
Spaltenreihenfolge beeinflusst Daten-Lokalität nicht
➡ Als erste Spalten in den Index. Reihenfolge so wählen,
dass andere Abfragen auch davon profitieren.
‣ Konjunktive ungleich Suchen sind schwierig
Spaltenreihenfolge beeinflusst Daten-Lokalität
➡ Nach den ist-gleich Bedingungen in den Index. Wenn
es mehrere ungleich Bedingungen gibt, die selektivste
zuerst in den Index.
13. Indizes nutzen:
Spaltenreihenfolge definiert Daten-Lokalität
Häufige Fehler:
‣ Willkürliche Spaltenreihenfolge ☠ (falsch)
“Einfach die Spalten der where-klausel in den Index”
➡ Stimmt nur bei konjunktiven ist-gleich Suchen
➡ Index ist für andere Abfragen oft nutzlos
‣ Selektivste Spalte zuerst ☠ (falsch)
“Spaltenreihenfolge entsprechend der Selektivität”
➡ Stimmt nur bei mehreren ungleich Bedingungen.
14. Indizes nutzen:
Spaltenreihenfolge definiert Daten-Lokalität
------------------------------------
| Id | Operation |
------------------------------------
| 0 | SELECT STATEMENT |
| 1 | TABLE ACCESS BY INDEX ROWID|
|* 2 | INDEX SKIP SCAN |
------------------------------------
Predicate Information:
------------------------------------
2 - access("B"=20 AND "A">25)
filter("B"=20)
Index on (A, B)
------------------------------------
| Id | Operation |
------------------------------------
| 0 | SELECT STATEMENT |
| 1 | TABLE ACCESS BY INDEX ROWID|
|* 2 | INDEX RANGE SCAN |
------------------------------------
Predicate Information:
------------------------------------
2 - access("B"=20 AND "A">25)
Index on (B, A)
Effizienteste
LösungEffizientester
workaround
‣ Index-Filterprälate sind ein Alarmzeichen
‣ Index Skip Scan ist ein Alarmzeichen
‣ Index Fast Full Scan ist ein Alarmzeichen
17. Using Indexes:
Angehängte Spalten verhindern Tabellenzugriff
All benötigten Spalten in den Index packen, um
Tabellenzugriff zu verhindern (Index-Only Scan).
‣ Kann Clustering-Factor ausgleichen
Daher weniger nützlich, wenn
➡ Der Clustering-Faktor nahe am Optimum ist oder
➡ nur wenige Zeilen selektiert werden
‣ Eine nicht indiziere Spalte -> Problem
Egal, wo die Spalte genannt wird (SELECT, ORDER BY,...)
➡ Allen oder nichts: einige Spalten der SELECT-Klausel
im Index nützt nichts.
18. Using Indexes:
Angehängte Spalten verhindern Tabellenzugriff
Häufige Fehler:
‣ Unnötige Spalten selektieren* ☠ (schlecht)
SELECT *? ORM-Tools in Verwendung?
➡ Viele Spalten in viele Indizes geben ist ein NO-GO.
‣ Übertreiben ☠ (schlecht)
➡ Index wird großer, Clustering Faktor (CF) schlechter
➡ Kleiner Nutzen bei gutem CF oder wenigen Zeilen
➡ Systembegrenzungen (32 Spalten, 6398 bytes@8k)
* http://use-the-index-luke.com/blog/2013-08/its-not-about-the-star-stupid
19. Zwei Schritte zum absolut besten Ausführungsplan:
1. Daten-Lokalität maximieren
‣ Der gute alte B-Tree Index ist das #1 Tool
‣ Partitionen sind überbewertet
‣ Tabellen-Cluster sind unterbewertet
Indizes und Queries müssen zusammenpassen
2. Abfrage optimal schreiben
‣ Explizite Bereichsbedingungen nutzen
‣ Top-N Abbrüche verwenden
‣ Index-Reihenfolge ausnutzen
Denke in
sortierten
Mengen
✓
✓
22. Beispiel:
Bestellungen von Gestern
1. Untere Grenze:
ORDER_DT >= TRUNC(sysdate-1)
2. Obere Grenze:
ORDER_DT < TRUNC(sysdate)
2. Explizite Bereichs-Bedingung verwenden
----------------------------------------------
| Id | Operation |
----------------------------------------------
| 0 | SELECT STATEMENT |
|* 1 | FILTER |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED |
|* 3 | INDEX RANGE SCAN |
----------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(TRUNC(SYSDATE@!)>TRUNC(SYSDATE@!-1))
3 - access("ORDER_DT">=TRUNC(SYSDATE@!-1)
AND "ORDER_DT"<TRUNC(SYSDATE@!))
23. Beispiel:
Bestellungen von Gestern
Häufiger Fehler:
‣TRUNC(order_dt)=:gestern ☠ (schlecht)
Die Intention wird “verschleiert”
➡ Erfordert funktions-basierten Index
CREATE INDEX … (TRUNC(order_dt));
➡ Unterstützt keine Sortierung nach order_dt
WHERE TRUNC(order_dt) = :gestern
ORDER BY order_dt DESC;
Index ist
nicht entsprechend sortiert
26. Beispiel:
Bestellungen von Gestern (rückläufig)
2. Abfrage schreiben — Reihenfolge nutzen
----------------------------------------------
| Id | Operation |
----------------------------------------------
| 0 | SELECT STATEMENT |
| 1 | SORT ORDER BY |
| 2 | TABLE ACCESS BY INDEX ROWID BATCHED |
|* 3 | INDEX RANGE SCAN |
----------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
3 - access("ORDERS"."SYS_NC00004$"=TRUNC(SYSDATE@!-1))
Kompromiss:
CPU
Memory
IO
1. Untere & obere Grenzen:
TRUNC(ORDER_DT)
= TRUNC(sysdate)-1
2. Sortieren
ORDER BY ORDER_DT DESC
27. Beispiel:
Bestellungen der letzten 24 Stunden
1. Daten-Lokalität mit TRUNC
* http://www.sqlfail.com/2014/05/05/oracle-can-now-use-function-based-indexes-in-queries-without-functions/
28. 2. Abfrage mit expliziter Bereichs-Bedingung
Beispiel:
Bestellungen der letzten 24 Stunden
-------------------------------------------------
| Id | Operation |
-------------------------------------------------
| 0 | SELECT STATEMENT |
|* 1 | TABLE ACCESS BY INDEX ROWID BATCHED |
|* 2 | INDEX RANGE SCAN on TRUNC(ORDER_DT) |
-------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("ORDER_DT">SYSDATE@!-1)
2 - access("ORDERS"."SYS_NC00004$">=TRUNC(SYSDATE@!-1))
2. Obere Grenze: keine (unbounded)
1. Untere Grenze:
ORDER_DT > sysdate - 1
Um FBI zu verwenden (vor 11.2.0.2*)
TRUNC(ORDER_DT)>=TRUNC(sysdate-1)
* http://www.sqlfail.com/2014/05/05/oracle-can-now-use-function-based-indexes-in-queries-without-functions/
29. Beispiel:
Bestellungen der letzten 24 Stunden
2. Abfrage mit expliziter Bereichs-Bedingung
1. Untere Grenze:
ORDER_DT > sysdate - 1
2. Obere Grenze: keine (unbounded)
--------------------------------------------
| Id | Operation |
--------------------------------------------
| 0 | SELECT STATEMENT |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED|
|* 2 | INDEX RANGE SCAN |
--------------------------------------------
Predicate Information:
----------------------
2 - access("ORDER_DT">SYSDATE@!-1)
30. Beispiel:
Bestellungen der letzten 24 Stunden
--------------------------------------------
| Id | Operation |
--------------------------------------------
| 0 | SELECT STATEMENT |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED|
|* 2 | INDEX RANGE SCAN |
--------------------------------------------
Predicate Information:
----------------------
2 - access("ORDER_DT">SYSDATE@!-1)
--------------------------------------------
| Id | Operation |
--------------------------------------------
| 0 | SELECT STATEMENT |
|* 1 | TABLE ACCESS BY INDEX ROWID BATCHED|
|* 2 | INDEX RANGE SCAN |
--------------------------------------------
Predicate Information:
----------------------
1 - filter("ORDER_DT">SYSDATE@!-1)
2 - access("ORDERS"."SYS_NC00004$">=TRUNC(SYSDATE@!-1))
Effizienteste
Lösung
Effizienter
workaround
31. Zwei Schritte zum absolut besten Ausführungsplan:
1. Daten-Lokalität maximieren
‣ Der gute alte B-Tree Index ist das #1 Tool
‣ Partitionen sind überbewertet
‣ Tabellen-Cluster sind unterbewertet
Indizes und Queries müssen zusammenpassen
2. Abfrage optimal schreiben
‣ Explizite Bereichsbedingungen nutzen
‣ Top-N Abbrüche verwenden
‣ Index-Reihenfolge ausnutzen
Denke in
sortierten
Mengen
✓
✓
✓
✓
38. Window-Funktionen für Top-N Abbruch
SELECT *
FROM (
SELECT orders.*
, DENSE_RANK() OVER (
ORDER BY TRUNC(order_dt) DESC
) rn
FROM orders
)
WHERE rn <= 1
ORDER BY order_dt DESC;
1 Gruppe Selektieren
39. Window-Funktionen für Top-N Abbruch
SELECT *
FROM (
SELECT orders.*
, DENSE_RANK() OVER (
ORDER BY TRUNC(order_dt) DESC
) rn
FROM orders
)
WHERE rn <= 1
ORDER BY order_dt DESC;
Nützlich um
hier abzubechen
40. Window-Funktionen für Top-N Abbruch
SELECT *
FROM (
SELECT orders.*
, DENSE_RANK() OVER (
ORDER BY TRUNC(order_dt) DESC
) rn
FROM orders
)
WHERE rn <= 1
ORDER BY order_dt DESC;
---------------------------------------------------------------------------
| Id | Operation | E-Rows | A-Rows | Buffers | Reads |
---------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2057 | 695 | 695 |
| 1 | SORT ORDER BY | 100K| 2057 | 695 | 695 |
|* 2 | VIEW | 100K| 2057 | 695 | 695 |
|* 3 | WINDOW NOSORT STOPKEY | 100K| 2057 | 695 | 695 |
| 4 | TABLE ACCESS BY INDEX ROWID| 100K| 2058 | 695 | 695 |
| 5 | INDEX FULL SCAN DESCENDING| 100K| 2058 | 8 | 8 |
---------------------------------------------------------------------------
DENSE_RANK
42. Top-N vs. Max()-Subquery
Häufige Fehler:
‣ Ex eaquo mit Sub-Queries ☠ (falsch)
WHERE (a, b)= (select max(a), max(b) ...)
➡ max() kommt von verschiedenen Zeilen!
➡ Nichts gefunden
‣ N>1 Gruppen selektieren ☠ (schlecht)
WHERE X < (SELECT MAX()...
WHERE X < (SELECT MAX()...))
WHERE (N-1) = (SELECT COUNT(DISTINCT(DT))...
43. Zwei Schritte zum absolut besten Ausführungsplan:
1. Daten-Lokalität maximieren
‣ Der gute alte B-Tree Index ist das #1 Tool
‣ Partitionen sind überbewertet
‣ Tabellen-Cluster sind unterbewertet
Indizes und Queries müssen zusammenpassen
2. Abfrage optimal schreiben
‣ Explizite Bereichsbedingungen nutzen
‣ Top-N Abbrüche verwenden
‣ Index-Reihenfolge ausnutzen
Denke in
sortierten
Mengen
✓
✓
✓
✓
✓
45. Beispiel:
Die nächsten 10 Bestellungen
2. Expliziter Bereich + Top-N Abbruch
1. Untere Grenze: keine (top-n)
2. Obere Grenze: wo wir aufgehört haben
WHERE ORDER_DT < :prev_dt
3. ORDER BY ORDER_DT DESC
4. FETCH FIRST 10 ROWS ONLY
46. Beispiel:
Die nächsten 10 Bestellungen
2. Expliziter Bereich + Top-N Abbruch
1. Untere Grenze: keine (top-n)
2. Obere Grenze: wo wir aufgehört haben
WHERE ORDER_DT < :prev_dt
3. ORDER BY ORDER_DT DESC
4. FETCH FIRST 10 ROWS ONLY
Ex aequo?
51. Explizite Bereichs-Bedingungen: Allgemein
Beispiel:
Die nächsten 10 Bestellungen
Oracle
limitation
Zwei semantisch
gleichwertige Ansätze:
X <= A
AND NOT(X=A AND Y>=B)
(X < A)
OR (X = A AND Y < B)
* http://use-the-index-luke.com/sql/partial-results/fetch-next-page#sb-equivalent-logic
☠
Schlechte Index-Nutzung*
52. Weitere Zeilen mit OFFSET holen
‣Nach FETCH FIRST...ROWS ONLY, hat
SQL:2011 OFFSET eingeführt.
‣Mit der ROWNUM Pseudo-Spalte kann man
auch Zeilen überspringen (ROWNUM > :x)
‣ROW_NUMBER() kann das ebenfalls.
Egal, wie man es schreibt ...
53. Weitere Zeilen mit OFFSET holen
OFFSET = SLEEP
Je größer die Zahl,
desto Langsamer die Abfage.
Noah schlimmer: Es belegt Ressourcen
und liefert falsche Ergebnisse.
54. Zwei Schritte zum absolut besten Ausführungsplan:
1. Daten-Lokalität maximieren
‣ Der gute alte B-Tree Index ist das #1 Tool
‣ Partitionen sind überbewertet
‣ Tabellen-Cluster sind unterbewertet
Indizes und Queries müssen zusammenpassen
2. Abfrage optimal schreiben
‣ Explizite Bereichsbedingungen nutzen
‣ Top-N Abbrüche verwenden
‣ Index-Reihenfolge ausnutzen
Denke in
sortierten
Mengen
✓
✓
✓
✓
✓