SlideShare ist ein Scribd-Unternehmen logo
1 von 60
Downloaden Sie, um offline zu lesen
www.informatik-aktuell.de
Den Suchraum

des Optimizers

gestalten
@MarkusWinand
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
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
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.
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
Indizes als Pyramiden
Visualize Simplify
Reihenfolge in mehrspaltigen Indizes
Indizes nutzen:
Spaltenreihenfolge definiert Daten-Lokalität
Example: WHERE	A	>	:a	AND	B	=	:b
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.
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.
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
Using Indexes:
Angehängte Spalten verhindern Tabellenzugriff
Beispiel: SELECT	C	FROM	X	WHERE	A	>	:a	AND	B	=	:b
Using Indexes:
Angehängte Spalten verhindern Tabellenzugriff
Beispiel: SELECT	C	FROM	X	WHERE	A	>	:a	AND	B	=	:b
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.
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
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
✓
✓
Beispiel:
Bestellungen von Gestern
CREATE	TABLE	orders	(

				...,

				order_dt	DATE	NOT	NULL,

				...

);	
INSERT	INTO	orders

							(...,	order_dt,	...)

VALUES	(...,	sysdate	,	...);							
100k Zeilen
Gleichmäßig über

4 Wochen verteilt
Beispiel:
Bestellungen von Gestern
1. Daten-Lokalität maximieren
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@!))
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
--------------------------------------	
|	Id	|	Operation																					|	
--------------------------------------	
|		0	|	SELECT	STATEMENT														|	
|*	1	|		FILTER																							|	
|		2	|			TABLE	ACCESS	BY	INDEX	ROWID	|	
|*	3	|				INDEX	RANGE	SCAN	DESCENDING|	
--------------------------------------	
Predicate	Information	(identified	by	operation	id):	
---------------------------------------------------	
			1	-	filter(TRUNC(SYSDATE@!)>TRUNC(SYSDATE@!-1))	
			3	-	access("ORDER_DT"<TRUNC(SYSDATE@!)	
										AND	"ORDER_DT">=TRUNC(SYSDATE@!-1))	
Beispiel:
Bestellungen von Gestern (rückläufig)
1. Untere & obere Grenzen:

ORDER_DT	>=	TRUNC(sysdate-1)

ORDER_DT	<		TRUNC(sysdate)	
2. Sortieren

ORDER	BY	ORDER_DT	DESC

2. Abfrage schreiben — Reihenfolge nutzen
--------------------------------------	
|	Id	|	Operation																					|	
--------------------------------------	
|		0	|	SELECT	STATEMENT														|	
|*	1	|		FILTER																							|	
|		2	|			TABLE	ACCESS	BY	INDEX	ROWID	|	
|*	3	|				INDEX	RANGE	SCAN	DESCENDING|	
--------------------------------------	
Predicate	Information	(identified	by	operation	id):	
---------------------------------------------------	
			1	-	filter(TRUNC(SYSDATE@!)>TRUNC(SYSDATE@!-1))	
			3	-	access("ORDER_DT"<TRUNC(SYSDATE@!)	
										AND	"ORDER_DT">=TRUNC(SYSDATE@!-1))	
Beispiel:
Bestellungen von Gestern (rückläufig)
2. Abfrage schreiben — Reihenfolge nutzen
1. Untere & obere Grenzen:

	TRUNC(ORDER_DT) 

=	TRUNC(sysdate)-1	
2. Sortieren

ORDER	BY	ORDER_DT	DESC

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

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/
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/
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)
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
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
✓
✓
✓
✓
Beispiel:
Die letzten 10 Bestellungen
1. Daten-Lokalität maximieren
Beispiel:
Die letzten 10 Bestellungen
2. Abfrage mit expliziten Bereichsbedingungen
1. Untere Grenze...? Nach 10 Zeilen...???	
2. Obere Grenze? sysdate? Keine!
Beispiel:
Die letzten 10 Bestellungen
1. Untere Grenze...? Nach 10 Zeilen...???	
2. Obere Grenze? sysdate? Keine!
2. Abfrage mit Top-N-Abbruch
3. Beginne mit: Aktuellsten

ORDER	BY	ORDER_DT	DESC
4. Abbruch nach: 10 Zeilen

FETCH	FIRST	10	ROWS	ONLY (seit 12c)
Beispiel:
Die letzten 10 Bestellungen
2. Abfrage mit Top-N-Abbruch
3. Beginne mit: Aktuellsten

ORDER	BY	ORDER_DT	DESC
4. Abbruch nach: 10 Zeilen

FETCH	FIRST	10	ROWS	ONLY (seit 12c)
----------------------------------------------------------	
|	Id		|	Operation																					|	A-Rows	|	Buffers	|	
----------------------------------------------------------	
|			0	|	SELECT	STATEMENT														|					10	|							8	|							
|*		1	|		VIEW																									|					10	|							8	|							
|*		2	|			WINDOW	NOSORT	STOPKEY							|					10	|							8	|							
|			3	|				TABLE	ACCESS	BY	INDEX	ROWID|					11	|							8	|							
|			4	|					INDEX	FULL	SCAN	DESCENDING|					11	|							3	|							
----------------------------------------------------------	
Predicate	Information	(identified	by	operation	id):	
---------------------------------------------------	
	1	-	filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=10)	
	2	-	filter(ROW_NUMBER()	OVER	(ORDER	BY	ORDER_DT	DESC)<=10)	ROW_NUMBER()	OVER	(ORDER	BY	ORDER_DT	DESC)<=10




								SELECT	orders.*

													,	ROW_NUMBER()	OVER	(

																			ORDER	BY	order_dt	DESC

															)	rn

										FROM	orders

							
Window-Funktionen für Top-N Abbruch
SELECT	*

		FROM	(

								SELECT	orders.*

													,	ROW_NUMBER()	OVER	(

																			ORDER	BY	order_dt	DESC

															)	rn

										FROM	orders

							)

	WHERE	rn	<=	10

	ORDER	BY	order_dt	DESC;
Window-Funktionen für Top-N Abbruch
10 Zeilen Selektieren
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
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
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
Window-Funktionen für Top-N Abbruch
---------------------------------------------------------------------------------	
|	Id	|	Operation																												|	E-Rows	|	A-Rows	|	Buffers	|	Reads	|	
---------------------------------------------------------------------------------	
|		0	|	SELECT	STATEMENT																					|								|			2057	|				1038	|			694	|	
|		1	|		SORT	ORDER	BY																							|			3448	|			2057	|				1038	|			694	|	
|		2	|			TABLE	ACCESS	BY	INDEX	ROWID	BATCHED|			3448	|			2057	|				1038	|			694	|	
|*	3	|				INDEX	RANGE	SCAN																		|			3448	|			2057	|						10	|					8	|	
|		4	|					SORT	AGGREGATE																			|						1	|						1	|							2	|					2	|	
|		5	|						INDEX	FULL	SCAN	(MIN/MAX)							|						1	|						1	|							2	|					2	|	
---------------------------------------------------------------------------------	
---------------------------------------------------------------------------	
|	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
SUB-SELECT
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))...
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
✓
✓
✓
✓
✓
1. Maximize data-locality
Beispiel:
Die nächsten 10 Bestellungen
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
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?
Explizite Bereichs-Bedingungen: Allgemein
Beispiel:
Die nächsten 10 Bestellungen
Explizite Bereichs-Bedingungen: Allgemein
Beispiel:
Die nächsten 10 Bestellungen
1. Eindeutige Sortierung
2. Row-Value Filter um
bereits gesehenes zu
entfernen (SQL-92)
3. Enter
Explizite Bereichs-Bedingungen: Allgemein
Beispiel:
Die nächsten 10 Bestellungen
Explizite Bereichs-Bedingungen: Allgemein
Beispiel:
Die nächsten 10 Bestellungen
(x,y)	=	(a,b)	
(x,y)	IN	((a,b),(c,d))	
(x,y)	<	(a,b)	
(x,y)	>	(a,b)
✓
✓
✗
✗
Oracle
limitation
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*
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 ...
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.
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
✓
✓
✓
✓
✓
Index Smart,
Not Hard
Über @MarkusWinand
Über @MarkusWinand
Über @MarkusWinand
€0,-
€10-30
Über @MarkusWinand
@ModernSQL
http://modern-sql.com

Weitere ähnliche Inhalte

Ähnlich wie Markus Winand – IT-Tage 2015 – Den Suchraum des Optimizers gestalten

Oracle-DB: Effizient Nutzung von function-based Indizes
Oracle-DB: Effizient Nutzung von function-based IndizesOracle-DB: Effizient Nutzung von function-based Indizes
Oracle-DB: Effizient Nutzung von function-based IndizesPeter Ramm
 
Puppet: Designing modules & repositories
Puppet: Designing modules & repositoriesPuppet: Designing modules & repositories
Puppet: Designing modules & repositoriesinovex GmbH
 
Oracle-DB: Systematische Rasterfahndung nach Performance-Antipattern
Oracle-DB: Systematische Rasterfahndung nach Performance-AntipatternOracle-DB: Systematische Rasterfahndung nach Performance-Antipattern
Oracle-DB: Systematische Rasterfahndung nach Performance-AntipatternPeter Ramm
 
Oracle workshop sessiontracing
Oracle workshop sessiontracingOracle workshop sessiontracing
Oracle workshop sessiontracingciganek
 
Das Repository-Pattern und der O/R-Mapper: Geniale Kombination oder vergebene...
Das Repository-Pattern und der O/R-Mapper: Geniale Kombination oder vergebene...Das Repository-Pattern und der O/R-Mapper: Geniale Kombination oder vergebene...
Das Repository-Pattern und der O/R-Mapper: Geniale Kombination oder vergebene...André Krämer
 
FROSCON 2011: MySQL Performance Tuning
FROSCON 2011: MySQL Performance TuningFROSCON 2011: MySQL Performance Tuning
FROSCON 2011: MySQL Performance TuningFromDual GmbH
 
Oracle Old Features DOAG 2011
Oracle Old Features DOAG 2011Oracle Old Features DOAG 2011
Oracle Old Features DOAG 2011Uwe Küchler
 
Puppet - Umgebungen, Daten & Code, Abhängigkeiten
Puppet - Umgebungen, Daten & Code, AbhängigkeitenPuppet - Umgebungen, Daten & Code, Abhängigkeiten
Puppet - Umgebungen, Daten & Code, Abhängigkeiteninovex GmbH
 
Eine Reise durch den PostgreSQL Optimizer
Eine Reise durch den PostgreSQL OptimizerEine Reise durch den PostgreSQL Optimizer
Eine Reise durch den PostgreSQL Optimizerpsoo1978
 
DOAG 2011: MySQL Performance Tuning
DOAG 2011: MySQL Performance TuningDOAG 2011: MySQL Performance Tuning
DOAG 2011: MySQL Performance TuningFromDual GmbH
 
Agiles Testing
Agiles TestingAgiles Testing
Agiles TestingNEOMO GmbH
 
Norbert Rieger – IT-Tage 2015 – Optimierung der Performance bei Oracle-Datenb...
Norbert Rieger – IT-Tage 2015 – Optimierung der Performance bei Oracle-Datenb...Norbert Rieger – IT-Tage 2015 – Optimierung der Performance bei Oracle-Datenb...
Norbert Rieger – IT-Tage 2015 – Optimierung der Performance bei Oracle-Datenb...Informatik Aktuell
 
Datentransfer mit Oracle Tools
Datentransfer mit Oracle ToolsDatentransfer mit Oracle Tools
Datentransfer mit Oracle ToolsChristian Gohmann
 
Content Inventur von großen Seiten - Barcamp 2014
Content Inventur von großen Seiten - Barcamp 2014Content Inventur von großen Seiten - Barcamp 2014
Content Inventur von großen Seiten - Barcamp 2014Marketing Factory
 
Datenbank-Hausputz für Einsteiger
Datenbank-Hausputz für EinsteigerDatenbank-Hausputz für Einsteiger
Datenbank-Hausputz für EinsteigerMarkus Flechtner
 
Webinar - Boost your ABAP
Webinar - Boost your ABAPWebinar - Boost your ABAP
Webinar - Boost your ABAPCadaxo GmbH
 
MySQL Performance Tuning für Oracle-DBA's
MySQL Performance Tuning für Oracle-DBA'sMySQL Performance Tuning für Oracle-DBA's
MySQL Performance Tuning für Oracle-DBA'sFromDual GmbH
 
Präsentation MySQL auf dem T3CM12
Präsentation MySQL auf dem T3CM12Präsentation MySQL auf dem T3CM12
Präsentation MySQL auf dem T3CM12Stefan Frömken
 
Lösungsorientierte Fehlerbehandlung
Lösungsorientierte FehlerbehandlungLösungsorientierte Fehlerbehandlung
Lösungsorientierte Fehlerbehandlungroskakori
 

Ähnlich wie Markus Winand – IT-Tage 2015 – Den Suchraum des Optimizers gestalten (20)

Oracle-DB: Effizient Nutzung von function-based Indizes
Oracle-DB: Effizient Nutzung von function-based IndizesOracle-DB: Effizient Nutzung von function-based Indizes
Oracle-DB: Effizient Nutzung von function-based Indizes
 
Puppet: Designing modules & repositories
Puppet: Designing modules & repositoriesPuppet: Designing modules & repositories
Puppet: Designing modules & repositories
 
Oracle-DB: Systematische Rasterfahndung nach Performance-Antipattern
Oracle-DB: Systematische Rasterfahndung nach Performance-AntipatternOracle-DB: Systematische Rasterfahndung nach Performance-Antipattern
Oracle-DB: Systematische Rasterfahndung nach Performance-Antipattern
 
Oracle workshop sessiontracing
Oracle workshop sessiontracingOracle workshop sessiontracing
Oracle workshop sessiontracing
 
Das Repository-Pattern und der O/R-Mapper: Geniale Kombination oder vergebene...
Das Repository-Pattern und der O/R-Mapper: Geniale Kombination oder vergebene...Das Repository-Pattern und der O/R-Mapper: Geniale Kombination oder vergebene...
Das Repository-Pattern und der O/R-Mapper: Geniale Kombination oder vergebene...
 
FROSCON 2011: MySQL Performance Tuning
FROSCON 2011: MySQL Performance TuningFROSCON 2011: MySQL Performance Tuning
FROSCON 2011: MySQL Performance Tuning
 
Oracle Old Features DOAG 2011
Oracle Old Features DOAG 2011Oracle Old Features DOAG 2011
Oracle Old Features DOAG 2011
 
Puppet - Umgebungen, Daten & Code, Abhängigkeiten
Puppet - Umgebungen, Daten & Code, AbhängigkeitenPuppet - Umgebungen, Daten & Code, Abhängigkeiten
Puppet - Umgebungen, Daten & Code, Abhängigkeiten
 
Eine Reise durch den PostgreSQL Optimizer
Eine Reise durch den PostgreSQL OptimizerEine Reise durch den PostgreSQL Optimizer
Eine Reise durch den PostgreSQL Optimizer
 
DOAG 2011: MySQL Performance Tuning
DOAG 2011: MySQL Performance TuningDOAG 2011: MySQL Performance Tuning
DOAG 2011: MySQL Performance Tuning
 
Agiles Testing
Agiles TestingAgiles Testing
Agiles Testing
 
Norbert Rieger – IT-Tage 2015 – Optimierung der Performance bei Oracle-Datenb...
Norbert Rieger – IT-Tage 2015 – Optimierung der Performance bei Oracle-Datenb...Norbert Rieger – IT-Tage 2015 – Optimierung der Performance bei Oracle-Datenb...
Norbert Rieger – IT-Tage 2015 – Optimierung der Performance bei Oracle-Datenb...
 
Datentransfer mit Oracle Tools
Datentransfer mit Oracle ToolsDatentransfer mit Oracle Tools
Datentransfer mit Oracle Tools
 
Content Inventur von großen Seiten - Barcamp 2014
Content Inventur von großen Seiten - Barcamp 2014Content Inventur von großen Seiten - Barcamp 2014
Content Inventur von großen Seiten - Barcamp 2014
 
NoSQL with MySQL
NoSQL with MySQLNoSQL with MySQL
NoSQL with MySQL
 
Datenbank-Hausputz für Einsteiger
Datenbank-Hausputz für EinsteigerDatenbank-Hausputz für Einsteiger
Datenbank-Hausputz für Einsteiger
 
Webinar - Boost your ABAP
Webinar - Boost your ABAPWebinar - Boost your ABAP
Webinar - Boost your ABAP
 
MySQL Performance Tuning für Oracle-DBA's
MySQL Performance Tuning für Oracle-DBA'sMySQL Performance Tuning für Oracle-DBA's
MySQL Performance Tuning für Oracle-DBA's
 
Präsentation MySQL auf dem T3CM12
Präsentation MySQL auf dem T3CM12Präsentation MySQL auf dem T3CM12
Präsentation MySQL auf dem T3CM12
 
Lösungsorientierte Fehlerbehandlung
Lösungsorientierte FehlerbehandlungLösungsorientierte Fehlerbehandlung
Lösungsorientierte Fehlerbehandlung
 

Markus Winand – IT-Tage 2015 – Den Suchraum des Optimizers gestalten

  • 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
  • 11. Indizes nutzen: Spaltenreihenfolge definiert Daten-Lokalität Example: WHERE A > :a AND B = :b
  • 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
  • 15. Using Indexes: Angehängte Spalten verhindern Tabellenzugriff Beispiel: SELECT C FROM X WHERE A > :a AND B = :b
  • 16. Using Indexes: Angehängte Spalten verhindern Tabellenzugriff Beispiel: SELECT C FROM X WHERE A > :a AND B = :b
  • 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 ✓ ✓
  • 21. Beispiel: Bestellungen von Gestern 1. Daten-Lokalität maximieren
  • 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 ✓ ✓ ✓ ✓
  • 32. Beispiel: Die letzten 10 Bestellungen 1. Daten-Lokalität maximieren
  • 33. Beispiel: Die letzten 10 Bestellungen 2. Abfrage mit expliziten Bereichsbedingungen 1. Untere Grenze...? Nach 10 Zeilen...??? 2. Obere Grenze? sysdate? Keine!
  • 34. Beispiel: Die letzten 10 Bestellungen 1. Untere Grenze...? Nach 10 Zeilen...??? 2. Obere Grenze? sysdate? Keine! 2. Abfrage mit Top-N-Abbruch 3. Beginne mit: Aktuellsten
 ORDER BY ORDER_DT DESC 4. Abbruch nach: 10 Zeilen
 FETCH FIRST 10 ROWS ONLY (seit 12c)
  • 35. Beispiel: Die letzten 10 Bestellungen 2. Abfrage mit Top-N-Abbruch 3. Beginne mit: Aktuellsten
 ORDER BY ORDER_DT DESC 4. Abbruch nach: 10 Zeilen
 FETCH FIRST 10 ROWS ONLY (seit 12c) ---------------------------------------------------------- | Id | Operation | A-Rows | Buffers | ---------------------------------------------------------- | 0 | SELECT STATEMENT | 10 | 8 | |* 1 | VIEW | 10 | 8 | |* 2 | WINDOW NOSORT STOPKEY | 10 | 8 | | 3 | TABLE ACCESS BY INDEX ROWID| 11 | 8 | | 4 | INDEX FULL SCAN DESCENDING| 11 | 3 | ---------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=10) 2 - filter(ROW_NUMBER() OVER (ORDER BY ORDER_DT DESC)<=10) ROW_NUMBER() OVER (ORDER BY ORDER_DT DESC)<=10
  • 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
  • 41. Window-Funktionen für Top-N Abbruch --------------------------------------------------------------------------------- | Id | Operation | E-Rows | A-Rows | Buffers | Reads | --------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 2057 | 1038 | 694 | | 1 | SORT ORDER BY | 3448 | 2057 | 1038 | 694 | | 2 | TABLE ACCESS BY INDEX ROWID BATCHED| 3448 | 2057 | 1038 | 694 | |* 3 | INDEX RANGE SCAN | 3448 | 2057 | 10 | 8 | | 4 | SORT AGGREGATE | 1 | 1 | 2 | 2 | | 5 | INDEX FULL SCAN (MIN/MAX) | 1 | 1 | 2 | 2 | --------------------------------------------------------------------------------- --------------------------------------------------------------------------- | 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 SUB-SELECT
  • 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 ✓ ✓ ✓ ✓ ✓
  • 44. 1. Maximize data-locality Beispiel: Die nächsten 10 Bestellungen
  • 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?
  • 48. Explizite Bereichs-Bedingungen: Allgemein Beispiel: Die nächsten 10 Bestellungen 1. Eindeutige Sortierung 2. Row-Value Filter um bereits gesehenes zu entfernen (SQL-92) 3. Enter
  • 50. Explizite Bereichs-Bedingungen: Allgemein Beispiel: Die nächsten 10 Bestellungen (x,y) = (a,b) (x,y) IN ((a,b),(c,d)) (x,y) < (a,b) (x,y) > (a,b) ✓ ✓ ✗ ✗ Oracle limitation
  • 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 ✓ ✓ ✓ ✓ ✓
  • 55.