Skalierbare Anwendungen erstellen
Entwerfen Sie Ihre Anwendungen so, dass sie unabhängig von Benutzerpopulationen und Datenvolumen dieselben Ressourcen verwenden und keine Systemressourcen überlasten.
Skalierbare Anwendungen
Eine skalierbare Anwendung kann eine größere Workload verarbeiten, wobei die Systemressourcennutzung proportional erhöht wird.
Eine skalierbare Anwendung kann eine größere Workload verarbeiten, wobei die Systemressourcennutzung proportional erhöht wird. Beispiel: Wenn Sie die Arbeitslast verdoppeln, verwendet eine skalierbare Anwendung doppelt so viele Systemressourcen.
Eine nicht skalierbare Anwendung erschöpft eine Systemressource. Wenn Sie daher die Anwendungs-Workload erhöhen, ist kein weiterer Durchsatz möglich. Nicht skalierbare Anwendungen führen zu festen Durchsätzen und schlechten Reaktionszeiten.
Beispiele für die Ressourcenauslastung:
-
Hardwareausschöpfung
-
Table Scans bei Transaktionen mit hohem Volumen, die unvermeidliche I/O-Engpässe (Disk Input/Output) verursachen
-
Übermäßige Netzwerkanforderungen, die zu Netzwerk- und Planungsengpässen führen
-
Speicherzuweisung, die Paging und Swapping verursacht
-
Übermäßige Prozess- und Thread-Zuweisung, die das Betriebssystem überfordert
Entwerfen Sie Ihre Anwendungen so, dass sie unabhängig von Benutzerpopulationen und Datenvolumen dieselben Ressourcen verwenden und keine Systemressourcen überlasten.
Bindungsvariablen zur Verbesserung der Skalierbarkeit verwenden
Mit Bind-Variablen, die korrekt verwendet werden, können Sie effiziente, skalierbare Anwendungen entwickeln.
Eine Bind-Variable ist ein Platzhalter in einer SQL-Anweisung, der durch eine gültige Wert- oder Wertadresse ersetzt werden muss, damit die Anweisung erfolgreich ausgeführt wird. Mit Bind-Variablen können Sie eine SQL-Anweisung schreiben, die zur Laufzeit Eingaben oder Parameter akzeptiert.
Ein Unterprogramm kann Parameter aufweisen, deren Werte vom Aufrufer bereitgestellt werden. Eine SQL-Anweisung kann Platzhalter für Bind-Variablen aufweisen, deren Werte (sog. Bind-Variablen) zur Laufzeit bereitgestellt werden. So wie ein Unterprogramm einmal kompiliert und dann mehrmals mit verschiedenen Parametern ausgeführt wird, wird eine SQL-Anweisung mit Platzhalterzeichen für Bind-Variablen einmal hart geparst und dann mit verschiedenen Bind-Variablen weich geparst.
Ein Hard Parse, der Optimierung und Generierung von Zeilenquellen umfasst, ist ein sehr CPU-intensiver Vorgang. Ein Soft Parse, der die Optimierung und Generierung von Zeilenquellen überspringt und direkt zur Ausführung führt, ist in der Regel viel schneller als ein Hard Parse derselben Anweisung. (Ein Überblick über die SQL-Verarbeitung, die den Unterschied zwischen einem Hard Parse und einem Soft Parse beinhaltet, finden Sie unter Oracle Database-Konzepte.)
Ein CPU-intensiver Vorgang wird nicht nur hart geparst, sondern ist auch ein nicht skalierbarer Vorgang, da er nicht gleichzeitig mit vielen anderen Vorgängen ausgeführt werden kann. Weitere Informationen zu Nebenläufigkeit und Skalierbarkeit finden Sie unter "Nebenläufigkeit und Skalierbarkeit".
Beispiel 8-1 zeigt den Performanceunterschied zwischen einer Abfrage ohne Bind-Variable und einer semantisch äquivalenten Abfrage mit einer Bind-Variable. Ersteres ist langsamer und verwendet viele weitere Latches (Informationen dazu, wie sich Latches auf die Skalierbarkeit auswirken, finden Sie unter). Um Performancestatistiken zu sammeln und anzuzeigen, verwendet das Beispiel das Runstats-Tool, das unter "Vergleichen von Programmierungstechniken mit Runstats" beschrieben wird.
Hinweis:
-
In Beispiel 8-1 sind die Performancekosten für einen einzelnen Benutzer dargestellt. Je mehr Benutzer hinzugefügt werden, desto schneller steigen die Kosten.
-
Das Ergebnis von Beispiel 8-1 wurde mit dieser Einstellung erstellt:
SET SERVEROUTPUT ON FORMAT TRUNCATED
Hinweis:
-
Die Verwendung von Bind-Variablen anstelle von Zeichenfolgenliteralen ist die effektivste Methode, um den Code für SQL-Injection-Angriffe unschädlich zu machen. Weitere Informationen finden Sie in der Oracle Database PL/SQL Language Reference.
-
Bind-Variablen reduzieren manchmal die Effizienz von Data Warehousing-Systemen. Da die meisten Abfragen so lange dauern, versucht der Optimizer, für jede Abfrage den besten Plan zu erstellen und nicht die beste generische Abfrage. Die Verwendung von Bind-Variablen zwingt den Optimizer manchmal, die beste generische Abfrage zu erstellen. Informationen zur Verbesserung der Performance in Data Warehousing-Systemen finden Sie in der Dokumentation Oracle Database Data Warehousing Guide.
Obwohl Soft Parsing effizienter ist als Hard Parsing, sind die Kosten für Soft Parsing einer Anweisung um ein Vielfaches immer noch sehr hoch. Um die Effizienz und Skalierbarkeit Ihrer Anwendung zu maximieren, minimieren Sie das Parsen. Die einfachste Möglichkeit, das Parsen zu minimieren, ist die Verwendung von PL/SQL.
Beispiel 8-1: Bindungsvariable verbessert Performance
CREATE TABLE t ( x VARCHAR2(5) );
DECLARE
TYPE rc IS REF CURSOR;
l_cursor rc;
BEGIN
runstats_pkg.rs_start; -- Collect statistics for query without bind variable
FOR i IN 1 .. 5000 LOOP
OPEN l_cursor FOR 'SELECT x FROM t WHERE x = ' || TO_CHAR(i);
CLOSE l_cursor;
END LOOP;
runstats_pkg.rs_middle; -- Collect statistics for query with bind variable
FOR i IN 1 .. 5000 LOOP
OPEN l_cursor FOR 'SELECT x FROM t WHERE x = :x' USING i;
CLOSE l_cursor;
END LOOP;
runstats_pkg.rs_stop(500); -- Stop collecting statistics
end;
/
Das Ergebnis sieht etwa folgendermaßen aus:
Run 1 ran in 740 hsec
Run 2 ran in 30 hsec
Run 1 ran in
2466.67% of the time of run 2
Name Run 1 Run 2 Difference
STAT...recursive cpu usage 729 19 -710
STAT...CPU used by this sessio 742 30 -712
STAT...parse time elapsed 1,051 4 -1,047
STAT...parse time cpu 1,066 2 -1,064
STAT...session cursor cache hi 1 4,998 4,997
STAT...table scans (short tabl 5,000 1 -4,999
STAT...parse count (total) 10,003 5,004 -4,999
LATCH.session idle bit 5,003 3 -5,000
LATCH.session allocation 5,003 3 -5,000
STAT...execute count 10,003 5,003 -5,000
STAT...opened cursors cumulati 10,003 5,003 -5,000
STAT...parse count (hard) 10,001 5 -9,996
STAT...CCursor + sql area evic 10,000 1 -9,999
STAT...enqueue releases 10,008 7 -10,001
STAT...enqueue requests 10,009 7 -10,002
STAT...calls to get snapshot s 20,005 5,006 -14,999
STAT...calls to kcmgcs 20,028 35 -19,993
STAT...consistent gets pin (fa 20,013 17 -19,996
LATCH.call allocation 20,002 6 -19,996
STAT...consistent gets from ca 20,014 18 -19,996
STAT...consistent gets 20,014 18 -19,996
STAT...consistent gets pin 20,013 17 -19,996
LATCH.simulator hash latch 20,014 11 -20,003
STAT...session logical reads 20,080 75 -20,005
LATCH.shared pool simulator 20,046 5 -20,041
LATCH.enqueue hash chains 20,343 15 -20,328
STAT...recursive calls 40,015 15,018 -24,997
LATCH.cache buffers chains 40,480 294 -40,186
STAT...session pga memory max 131,072 65,536 -65,536
STAT...session pga memory 131,072 65,536 -65,536
LATCH.row cache objects 165,209 139 -165,070
STAT...session uga memory max 219,000 0 -219,000
LATCH.shared pool 265,108 152 -264,956
STAT...logical read bytes from 164,495,360 614,400 -163,880,960
Run 1 latches total compared to run 2 -- difference and percentage
Run 1 Run 2 Diff Pct
562,092 864 -561,228 2,466.67%
PL/SQL procedure successfully completed.
Skalierbarkeit mit PL/SQL verbessern
Bestimmte PL/SQL-Features können Ihnen helfen, die Skalierbarkeit von Anwendungen zu verbessern.
Parsing durch PL/SQL minimieren
PL/SQL, das für den Datenbankzugriff optimiert ist, speichert Anweisungen automatisch im Cache. Wenn Sie einen Cursor in PL/SQL schließen, wird der Cursor aus Ihrer Perspektive geschlossen. Das heißt, Sie können ihn nicht dort verwenden, wo ein offener Cursor erforderlich ist. PL/SQL hält den Cursor jedoch tatsächlich offen und speichert seine Anweisung im Cache.
Wenn Sie die gecachte Anweisung erneut verwenden, verwendet PL/SQL denselben Cursor, wodurch ein Parse vermieden wird. (PL/SQL schließt gecachte Anweisungen, falls erforderlich. Beispiel: Wenn das Programm einen anderen Cursor öffnen muss, dies jedoch die init.ora-Einstellung von OPEN_CURSORS überschreitet.)
PL/SQL kann nur SQL-Anweisungen im Cache speichern, die zur Laufzeit nicht geändert werden können.
Info zur Anweisung EXECUTE IMMEDIATE
Die EXECUTE IMMEDIATE-Anweisung erstellt und führt eine dynamische SQL-Anweisung in einem einzigen Vorgang aus.
Die allgemeine Syntax der EXECUTE IMMEDIATE-Anweisung lautet:
EXECUTE IMMEDIATE sql_statement
sql_statement ist eine Zeichenfolge, die eine SQL-Anweisung darstellt. Wenn sql_statement bei jeder Ausführung der EXECUTE IMMEDIATE-Anweisung denselben Wert aufweist, kann PL/SQL die EXECUTE IMMEDIATE-Anweisung cachen. Wenn sql_statement bei jeder Ausführung der EXECUTE IMMEDIATE-Anweisung unterschiedlich sein kann, kann PL/SQL die EXECUTE IMMEDIATE-Anweisung nicht cachen.
Siehe:
-
Oracle Database PL/SQL Language Reference: Informationen zu EXECUTE IMMEDIATE
Informationen zu OPEN FOR-Anweisungen
Die OPEN FOR-Anweisung weist die folgende grundlegende Syntax auf:
Die allgemeine Syntax der OPEN FOR-Anweisung lautet:
OPEN cursor_variable FOR query
Ihre Anwendung kann cursor_variable für mehrere verschiedene Abfragen öffnen, bevor Sie sie schließen. Da PL/SQL die Anzahl verschiedener Abfragen erst zur Laufzeit bestimmen kann, kann PL/SQL die OPEN FOR-Anweisung nicht cachen.
Wenn Sie keine Cursorvariable verwenden müssen, verwenden Sie einen deklarierten Cursor, um die Performance zu verbessern und die Programmierung zu vereinfachen. Weitere Informationen finden Sie im Oracle Database Development Guide.
Siehe:
-
Oracle Database PL/SQL Language Reference: Informationen zu OPEN FOR
DBMS_SQL-Package
Das DBMS_SQL-Package ist eine API zum Erstellen, Ausführen und Beschreiben dynamischer SQL-Anweisungen. Sie müssen das DBMS_SQL-Package anstelle der EXECUTE IMMEDIATE-Anweisung verwenden, wenn der PL/SQL-Compiler bei der Kompilierung die Anzahl oder Typen von Ausgabehostvariablen (Listenelemente auswählen) oder Eingabe-Bind-Variablen nicht bestimmen kann.
Das DBMS_SQL-Package ist eine API zum Erstellen, Ausführen und Beschreiben dynamischer SQL-Anweisungen. Die Verwendung des DBMS_SQL-Packages erfordert mehr Aufwand als die Verwendung der Anweisung EXECUTE IMMEDIATE. Sie müssen jedoch das DBMS_SQL-Package verwenden, wenn der PL/SQL-Compiler bei der Kompilierung die Anzahl oder Typen von Ausgabehostvariablen (Listenelemente auswählen) oder Eingabe-Bind-Variablen nicht bestimmen kann.
Siehe:
-
Oracle Database PL/SQL Language Reference für weitere Informationen zur Verwendung des DBMS_SQL-Packages
-
Oracle Database PL/SQL Packages and Types Reference für vollständige Informationen zum DBMS_SQL-Package
Informationen zu Bulk SQL
Bulk SQL reduziert die Anzahl der "Roundtrips" zwischen PL/SQL und SQL, wodurch weniger Ressourcen verwendet werden.
Ohne Bulk-SQL rufen Sie jeweils eine Zeile aus der Datenbank (SQL) ab, verarbeiten sie (PL/SQL) und geben sie an die Datenbank (SQL) zurück. Mit Bulk SQL rufen Sie eine Gruppe von Zeilen aus der Datenbank ab, verarbeiten die Zeilengruppe und geben dann die gesamte Gruppe an die Datenbank zurück.
Oracle empfiehlt die Verwendung von Bulk SQL, wenn Sie mehrere Zeilen aus der Datenbank abrufen und sie an die Datenbank zurückgeben, wie in Beispiel 8-2. Sie benötigen kein Bulk-SQL, wenn Sie mehrere Zeilen abrufen, sie jedoch nicht zurückgeben. Beispiel:
FOR x IN (SELECT * FROM t WHERE ... ) -- Retrieve row set (implicit array fetch)
LOOP
DBMS_OUTPUT.PUT_LINE(t.x); -- Process rows but do not return them
END LOOP;
Beispiel 8-2 durchläuft eine Tabelle t mit einem Spaltenobjektnamen, ruft 100 Zeilen ab, verarbeitet sie und gibt sie an die Datenbank zurück. (Um die Bulk-FETCH-Anweisung auf 100 Zeilen zu beschränken, ist ein expliziter Cursor erforderlich.)
In Beispiel 8-3 wird derselbe Job wie in Beispiel 8-2 ohne Bulk-SQL ausgeführt.
Wie diese TKPROF-Berichte für Beispiel 8-2 und Beispiel 8-3 zeigen, verbraucht die Verwendung von Bulk-SQL für diesen Job fast 50% weniger CPU-Zeit:
SELECT ROWID RID, OBJECT_NAME FROM T T_BULK
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 721 0.17 0.17 0 22582 0 71825
********************************************************************************
UPDATE T SET OBJECT_NAME = :B1 WHERE ROWID = :B2
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 719 12.83 13.77 0 71853 74185 71825
Fetch 0 0.00 0.00 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 720 12.83 13.77 0 71853 74185 71825
SELECT ROWID RID, OBJECT_NAME FROM T T_SLOW_BY_SLOW
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 721 0.17 0.17 0 22582 0 71825
********************************************************************************
UPDATE T SET OBJECT_NAME = :B2 WHERE ROWID = :B1
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 71824 21.25 22.25 0 71836 73950 71824
Fetch 0 0.00 0.00 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 71825 21.25 22.25 0 71836 73950 71824
Die Verwendung von Bulk-SQL für diesen Job verwendet jedoch mehr CPU-Zeit und mehr Code als eine einzelne SQL-Anweisung, wie der folgende TKPROF-Bericht zeigt:
UPDATE T SET OBJECT_NAME = SUBSTR(OBJECT_NAME,2) || SUBSTR(OBJECT_NAME,1,1)
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 1.30 1.44 0 2166 75736 71825
Fetch 0 0.00 0.00 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 2 1.30 1.44 0 2166 75736 71825
Beispiel 8-2: Bulk-SQL
CREATE OR REPLACE PROCEDURE bulk AS
TYPE ridArray IS TABLE OF ROWID;
TYPE onameArray IS TABLE OF t.object_name%TYPE;
CURSOR c is SELECT ROWID rid, object_name -- explicit cursor
FROM t t_bulk;
l_rids ridArray;
l_onames onameArray;
N NUMBER := 100;
BEGIN
OPEN c;
LOOP
FETCH c BULK COLLECT
INTO l_rids, l_onames LIMIT N; -- retrieve N rows from t
FOR i in 1 .. l_rids.COUNT
LOOP -- process N rows
l_onames(i) := substr(l_onames(i),2) || substr(l_onames(i),1,1);
END LOOP;
FORALL i in 1 .. l_rids.count -- return processed rows to t
UPDATE t
SET object_name = l_onames(i)
WHERE ROWID = l_rids(i);
EXIT WHEN c%NOTFOUND;
END LOOP;
CLOSE c;
END;
/
Beispiel 8-3 ohne Bulk SQL
CREATE OR REPLACE PROCEDURE slow_by_slow AS
BEGIN
FOR x IN (SELECT rowid rid, object_name FROM t t_slow_by_slow)
LOOP
x.object_name := substr(x.object_name,2) || substr(x.object_name,1,1);
UPDATE t
SET object_name = x.object_name
WHERE rowid = x.rid;
END LOOP;
END;
Siehe:
-
Oracle Database Development Guide für einen Überblick über Bulk-SQL
-
Oracle Database Development Guide für spezifischere Informationen zur Verwendung von Bulk-SQL
-
Oracle Database PL/SQL Language Reference, um weitere Informationen zu Bulk-SQL zu erhalten
Nebenläufigkeit und Skalierbarkeit
Nebenläufigkeit ist die gleichzeitige Ausführung mehrerer Transaktionen. Je besser Ihre Anwendung mit gleichzeitigem Zugriff umgeht, desto skalierbarer ist sie. Eine skalierbare Anwendung kann eine größere Workload verarbeiten, wobei die Systemressourcennutzung proportional erhöht wird.
Nebenläufigkeit ist die gleichzeitige Ausführung mehrerer Transaktionen. Anweisungen in gleichzeitigen Transaktionen können dieselben Daten aktualisieren. Je besser Ihre Anwendung mit gleichzeitigem Zugriff umgeht, desto skalierbarer ist sie. Eine skalierbare Anwendung kann eine größere Workload verarbeiten, wobei die Systemressourcennutzung proportional erhöht wird. Beispiel: Wenn Sie die Arbeitslast verdoppeln, verwendet eine skalierbare Anwendung doppelt so viele Systemressourcen.
Gleichzeitige Transaktionen müssen aussagekräftige und konsistente Ergebnisse liefern. Daher muss eine Mehrbenutzerdatenbank Folgendes bereitstellen:
-
Datennebenläufigkeit, mit der sichergestellt wird, dass Benutzer gleichzeitig auf Daten zugreifen können.
-
Datenkonsistenz, die sicherstellt, dass jeder Benutzer eine konsistente Ansicht der Daten sieht, einschließlich sichtbarer Änderungen aus seinen eigenen Transaktionen und festgeschriebenen Transaktionen anderer Benutzer
Oracle Database sorgt für Datenkonsistenz, indem ein Konsistenzmodell mit mehreren Versionen und verschiedene Arten von Sperren und Transaktionsisolationsebenen verwendet werden. Einen Überblick über den Sperrmechanismus von Oracle Database finden Sie in Oracle Database Concepts. Einen Überblick über die Isolationsebenen von Oracle Database-Transaktionen finden Sie unter Oracle Database-Konzepte.
Um ein konsistentes Transaktionsverhalten zu beschreiben, wenn Transaktionen gleichzeitig ausgeführt werden, haben Datenbankforscher eine Transaktionsisolationskategorie definiert, die als serialisierbar bezeichnet wird. Eine serialisierbare Transaktion wird in einer Umgebung ausgeführt, die scheinbar eine Einzelbenutzerdatenbank ist. Serialisierbare Transaktionen sind in bestimmten Fällen wünschenswert, aber für 99% der Arbeitslast ist die Isolierung von Read Committed perfekt.
Oracle Database verfügt über Features, die den gleichzeitigen Zugriff und die Skalierbarkeit verbessern, z.B. Sequences, Latches, nicht blockierende Lese- und Schreibvorgänge und Shared SQL.
Siehe auch: Oracle Database-Konzepte für weitere Informationen zu gleichzeitigem Datenzugriff und -konsistenz
Sequences und Nebenläufigkeit
Sequences eliminieren die Serialisierung und verbessern so den gleichzeitigen Zugriff und die Skalierbarkeit Ihrer Anwendung.
Eine Sequenz ist ein Schemaobjekt, aus dem mehrere Benutzer eindeutige Ganzzahlen generieren können. Dies ist sehr nützlich, wenn Sie eindeutige Primärschlüssel benötigen.
Ohne Sequenzen müssen eindeutige Primärschlüsselwerte programmgesteuert erzeugt werden. Ein Benutzer erhält einen neuen Primärschlüsselwert, indem er den zuletzt erzeugten Wert auswählt und inkrementiert. Diese Technik erfordert eine Sperre während der Transaktion und führt dazu, dass mehrere Benutzer auf den nächsten Primärschlüsselwert warten, d.h. die Transaktionen serialisieren. Sequences eliminieren die Serialisierung und verbessern so den gleichzeitigen Zugriff und die Skalierbarkeit Ihrer Anwendung.
Siehe:
-
Oracle Database Concepts für Informationen zum gleichzeitigen Zugriff auf Sequences
Latches und Nebenläufigkeit
Eine Erhöhung der Latches bedeutet mehr nebenläufigkeitsbasierte Waits und damit eine Verringerung der Skalierbarkeit.
Ein Latch ist ein einfacher Serialisierungsmechanismus auf niedriger Ebene, der den Mehrbenutzerzugriff auf gemeinsame Datenstrukturen koordiniert. Latches schützen Shared Memory-Ressourcen vor Beschädigung, wenn mehrere Prozesse auf sie zugreifen.
Eine Erhöhung der Latches bedeutet mehr nebenläufigkeitsbasierte Waits und damit eine Verringerung der Skalierbarkeit. Wenn Sie einen Ansatz verwenden können, der während der Entwicklung etwas schneller ausgeführt wird, oder einen Ansatz, der weniger Latches verwendet, verwenden Sie diesen Ansatz.
Siehe:
-
Oracle Database-Konzepte für Informationen zu Latches
-
Oracle Database-Konzepte für Informationen zu Mutexen, die wie Latches für einzelne Objekte sind
Nicht blockierende Lese- und Schreibvorgänge sowie Nebenläufigkeit
In Oracle Database können nicht blockierende Lese- und Schreibvorgänge Abfragen gleichzeitig mit Änderungen an den Daten ausführen, die sie lesen, ohne zu blockieren oder zu stoppen. Durch nicht blockierende Lese- und Schreibvorgänge kann eine Session Daten lesen, während eine andere Session diese Daten ändert.
Shared SQL und Nebenläufigkeit
Oracle Database kompiliert eine SQL-Anweisung einmal in ein ausführbares Objekt, und dann können andere Sessions das Objekt wiederverwenden, solange es vorhanden ist. Mit diesem Oracle Database-Feature, das als Shared SQL bezeichnet wird, kann die Datenbank sehr ressourcenintensive Vorgänge kompilieren und SQL-Anweisungen nur einmal optimieren, anstatt jedes Mal, wenn eine Session dieselbe SQL-Anweisung verwendet.
Siehe auch: Oracle Database-Konzepte für weitere Informationen zu Shared SQL
Anzahl gleichzeitiger Sessions begrenzen
Je mehr gleichzeitige Sessions Sie haben, desto mehr nebenläufigkeitsbasierte Waits haben Sie und desto langsamer ist Ihre Antwortzeit.
Wenn Ihr Computer über n CPU-Cores verfügt, können maximal n Sessions gleichzeitig aktiv sein. Jede weitere "gleichzeitige" Session muss warten, bis ein CPU-Core verfügbar ist, bevor er aktiv werden kann. Wenn einige wartende Sessions nur auf I/O warten, kann eine Erhöhung der Anzahl gleichzeitiger Sessions auf etwas mehr als n die Laufzeitperformance etwas verbessern. Wenn Sie jedoch die Anzahl gleichzeitiger Sessions zu stark erhöhen, wird die Laufzeitperformance erheblich reduziert.
Der Initialisierungsparameter SESSIONS bestimmt die maximale Anzahl gleichzeitiger Benutzer im System. Weitere Informationen finden Sie in der Oracle Database-Referenz.
Siehe auch: http://www.youtube.com/watch?v=xNDnVOCdvQ0 für ein Video, das zeigt, wie sich die Anzahl gleichzeitiger Sessions auf einem Computer mit 12 CPU-Kernen von Tausenden auf 96 reduzieren lässt
Vergleich von Programmierungstechniken mit Runstats
Mit dem Runstats-Tool können Sie die Leistung von zwei Programmierungstechniken vergleichen, um zu sehen, welche besser ist.
Über Runstats
Mit dem Runstats-Tool können Sie die Leistung von zwei Programmierungstechniken vergleichen, um zu sehen, welche besser ist.
Runstats misst die folgenden Werte:
-
Verstrichene Zeit für jede Technik in Hundertstelsekunden (Sek.)
-
Verstrichene Zeit für die erste Technik als Prozentsatz der zweiten Technik
-
Systemstatistiken für die beiden Techniken (z. B. Parse-Aufrufe)
-
Verriegelung für die beiden Techniken
Von den vorhergehenden Messungen ist das Verriegeln am wichtigsten (siehe "Info zu Latches und Nebenläufigkeit").
Siehe auch: Beispiel 8-1, das Runstats verwendet
Runstats einrichten
Das Runstats-Tool wird als Paket implementiert, das eine View und eine temporäre Tabelle verwendet.
Hinweis: Für Schritt 1 der folgenden Prozedur benötigen Sie die SELECT-Berechtigung für die dynamischen Performanceansichten V$STATNAME, V$MYSTAT und V$LATCH. Wenn Sie diese Berechtigung nicht erhalten können, erstellen Sie die Ansicht in Schritt 1, und erteilen Sie Ihnen die Berechtigung SELECT.
Schritte zum Einrichten des Runstats-Tools:
-
Erstellen Sie die Ansicht, die Runstats verwendet:
CREATE OR REPLACE VIEW stats AS SELECT 'STAT...' || a.name name, b.value FROM V$STATNAME a, V$MYSTAT b WHERE a.statistic# = b.statistic# UNION ALL SELECT 'LATCH.' || name, gets FROM V$LATCH; -
Erstellen Sie die temporäre Tabelle, die Runstats verwendet:
DROP TABLE run_stats; CREATE GLOBAL TEMPORARY TABLE run_stats ( runid VARCHAR2(15), name VARCHAR2(80), value INT ) ON COMMIT PRESERVE ROWS; -
Erstellen Sie die Package-Spezifikation:
CREATE OR REPLACE PACKAGE runstats_pkg AS PROCEDURE rs_start; PROCEDURE rs_middle; PROCEDURE rs_stop( p_difference_threshold IN NUMBER DEFAULT 0 ); end; /Der Parameter
p_difference_thresholdsteuert die Anzahl der Statistiken und Verriegelungsdaten, die Runstats anzeigt. Runstats zeigt Daten nur an, wenn der Unterschied für die beiden Techniken größer alsp_difference_thresholdist. Standardmäßig zeigt Runstats alle Daten an. -
Erstellen Sie den folgenden Packagebody:
CREATE OR REPLACE PACKAGE BODY runstats_pkg AS g_start NUMBER; g_run1 NUMBER; g_run2 NUMBER; PROCEDURE rs_start IS BEGIN DELETE FROM run_stats; INSERT INTO run_stats SELECT 'before', stats.* FROM stats; g_start := DBMS_UTILITY.GET_TIME; END rs_start; PROCEDURE rs_middle IS BEGIN g_run1 := (DBMS_UTILITY.GET_TIME - g_start); INSERT INTO run_stats SELECT 'after 1', stats.* FROM stats; g_start := DBMS_UTILITY.GET_TIME; END rs_middle; PROCEDURE rs_stop( p_difference_threshold IN NUMBER DEFAULT 0 ) IS BEGIN g_run2 := (DBMS_UTILITY.GET_TIME - g_start); DBMS_OUTPUT.PUT_LINE ('Run 1 ran in ' || g_run1 || ' hsec'); DBMS_OUTPUT.PUT_LINE ('Run 2 ran in ' || g_run2 || ' hsec'); DBMS_OUTPUT.PUT_LINE ('Run 1 ran in ' || round(g_run1/g_run2*100, 2) || '% of the time of run 2'); DBMS_OUTPUT.PUT_LINE( CHR(9) ); INSERT INTO run_stats SELECT 'after 2', stats.* FROM stats; DBMS_OUTPUT.PUT_LINE ( RPAD( 'Name', 30 ) || LPAD( 'Run 1', 14) || LPAD( 'Run 2', 14) || LPAD( 'Difference', 14) ); FOR x IN ( SELECT RPAD( a.name, 30 ) || TO_CHAR( b.value - a.value, '9,999,999,999' ) || TO_CHAR( c.value - b.value, '9,999,999,999' ) || TO_CHAR( ( (c.value - b.value) - (b.value - a.value)), '9,999,999,999' ) data FROM run_stats a, run_stats b, run_stats c WHERE a.name = b.name AND b.name = c.name AND a.runid = 'before' AND b.runid = 'after 1' AND c.runid = 'after 2' AND (c.value - a.value) > 0 AND abs((c.value - b.value) - (b.value - a.value)) > p_difference_threshold ORDER BY ABS((c.value - b.value) - (b.value - a.value)) ) LOOP DBMS_OUTPUT.PUT_LINE( x.data ); END LOOP; DBMS_OUTPUT.PUT_LINE( CHR(9) ); DBMS_OUTPUT.PUT_LINE( 'Run 1 latches total compared to run 2 -- difference and percentage' ); DBMS_OUTPUT.PUT_LINE ( LPAD( 'Run 1', 14) || LPAD( 'Run 2', 14) || LPAD( 'Diff', 14) || LPAD( 'Pct', 10) ); FOR x IN ( SELECT TO_CHAR( run1, '9,999,999,999' ) || TO_CHAR( run2, '9,999,999,999' ) || TO_CHAR( diff, '9,999,999,999' ) || TO_CHAR( ROUND( g_run1/g_run2*100, 2), '99,999.99' ) || '%' data FROM ( SELECT SUM (b.value - a.value) run1, SUM (c.value - b.value) run2, SUM ( (c.value - b.value) - (b.value - a.value)) diff FROM run_stats a, run_stats b, run_stats c WHERE a.name = b.name AND b.name = c.name AND a.runid = 'before' AND b.runid = 'after 1' AND c.runid = 'after 2' AND a.name like 'LATCH%' ) ) LOOP DBMS_OUTPUT.PUT_LINE( x.data ); END LOOP; END rs_stop; END; /
Siehe:
-
Oracle Database Reference für Informationen zu dynamischen Performance-Views
Runstats verwenden
In diesem Thema wird die Syntax für die Verwendung des Runstats-Tools beschrieben.
Um mit Runstats zwei Programmiertechniken zu vergleichen, rufen Sie die runstats_pkg-Prozeduren aus einem anonymen Block mit der folgenden Syntax auf:
[ DECLARE local_declarations ]
BEGIN
runstats_pkg.rs_start;
code_for_first_technique
runstats_pkg.rs_middle;
code_for_second_technique
runstats_pkg.rs_stop(n);
END;
/
Siehe auch: Beispiel 8-1, das Runstats verwendet
Real-World Performance und Datenverarbeitungstechniken
Eine häufige Aufgabe in Datenbankanwendungen in einer Data Warehouse-Umgebung besteht darin, ein großes Dataset abzufragen oder zu ändern. Das Problem für Anwendungsentwickler besteht darin, bei der Verarbeitung großer Datasets eine hohe Performance zu erreichen.
Verarbeitungstechniken lassen sich in zwei Kategorien einteilen: iterativ und setbasiert. Im Laufe der Jahre hat die Real-World Performance-Gruppe festgestellt, dass setbasierte Verarbeitungstechniken Größenordnungen besser ausführen für Datenbankanwendungen, die große Datasets verarbeiten.
Dieses Thema umfasst die folgenden Hauptunterthemen:
Informationen zur iterativen Datenverarbeitung
Bei der iterativen Verarbeitung verwenden Anwendungen bedingte Logik, um eine Reihe von Zeilen zu durchlaufen.
Normalerweise verwendet die iterative Verarbeitung, wenn auch nicht unbedingt, wie folgt ein Client/Server-Modell:
-
Übertragen Sie eine Gruppe von Zeilen vom Datenbankserver an die Client-Anwendung.
-
Verarbeiten Sie die Gruppe innerhalb der Clientanwendung.
-
Übertragen Sie die verarbeitete Gruppe wieder auf den Datenbankserver.
Sie können iterative Algorithmen mit drei Haupttechniken implementieren: Row-by-Row-Verarbeitung, Array-Verarbeitung und manuelle Parallelisierung.
Iterative Verarbeitung: Zeile für Zeile
Bei der Row-by-Row-Verarbeitung durchläuft ein einzelner Prozess ein Dataset und wird jeweils für eine einzelne Zeile ausgeführt. In einer typischen Implementierung ruft die Anwendung jede Zeile aus der Datenbank ab, verarbeitet sie in der Middle Tier und sendet die Zeile dann zurück an die Datenbank, die DML und Commits ausführt.
Angenommen, Ihre funktionale Anforderung besteht darin, eine externe Tabelle namens ext_scan_events abzufragen und die zugehörigen Zeilen dann in eine Heap-organisierte Staging-Tabelle namens stage1_scan_events einzufügen. Der folgende PL/SQL-Block verwendet eine Row-by-Row-Technik, um diese Anforderung zu erfüllen:
declare
cursor c is select s.* from ext_scan_events s;
r c%rowtype;
begin
open c;
loop
fetch c into r;
exit when c%notfound;
insert into stage1_scan_events d values r;
commit;
end loop;
close c;
end;
Die Row-by-Row-Technik hat folgende Vorteile:
-
Es funktioniert gut bei kleinen Datensätzen.
-
Der Looping-Algorithmus ist allen professionellen Entwicklern vertraut, schnell zu schreiben und leicht zu verstehen.
Die Row-by-Row-Technik hat folgende Nachteile:
-
Die Verarbeitungszeit kann bei großen Datensätzen inakzeptabel lang sein.
-
Die Anwendung wird seriell ausgeführt und kann daher die nativen parallelen Verarbeitungsfeatures von Oracle Database, die auf moderner Hardware ausgeführt werden, nicht nutzen.
Siehe auch: RWP #7 Set-Based Processing
Iterative Verarbeitung: Arrays
Die Array-Verarbeitung ist mit der zeilenweisen Verarbeitung identisch. Der Unterschied besteht darin, dass sie eine Gruppe von Datensätzen in jeder Iteration und nicht einen einzelnen Datensatz verarbeitet.
Beispiel X-X: Fragen Sie eine externe Tabelle mit dem Namen ext_scan_events ab, und fügen Sie die zugehörigen Zeilen in eine Heap-organisierte Staging-Tabelle mit dem Namen stage1_scan_events ein. Der folgende PL/SQL-Block verwendet eine Array-Technik, um diese Anforderung zu erfüllen:
declare
cursor c is select s.* from ext_scan_events s;
type t is table of c%rowtype index by binary_integer;
a t;
rows binary_integer := 0;
begin
open c;
loop
fetch c bulk collect into a limit array_size;
exit when a.count = 0;
forall i in 1..a.count
insert into stage1_scan_events d values a(i);
commit;
end loop;
close c;
end;
Der vorhergehende Code unterscheidet sich vom äquivalenten Row-by-Row-Code bei der Verwendung eines BULK COLLECT-Operators in der FETCH STATEMENT, der durch den Wert array_size des Typs PLS_INTEGER begrenzt ist. Beispiel: Wenn array_size auf 100 gesetzt ist, ruft die Anwendung Zeilen in Gruppen von 100 ab.
Die Array-Technik hat gegenüber der Row-by-Row-Technik folgende Vorteile:
-
Das Array ermöglicht es der Anwendung, eine Gruppe von Zeilen gleichzeitig zu verarbeiten. Dies bedeutet, dass Netzwerk-Roundtrips, COMMIT-Zeit und der Codepfad im Client und Server reduziert werden.
-
Die Datenbank ist effizienter, da der Serverprozess die Einfügungen batches und nach jeder Gruppe von Einfügungen statt nach jeder Einfügung festschreibt.
Die Nachteile dieser Technik sind die gleichen wie bei der Row-by-Row-Verarbeitung. Die Verarbeitungszeit kann für große Datasets nicht akzeptabel sein. Außerdem muss die Anwendung seriell auf einem einzelnen CPU-Core ausgeführt werden, sodass die native Parallelität von Oracle Database nicht ausgenutzt werden kann.
Iterative Verarbeitung: Manuelle Parallelität
Bei der manuellen Parallelisierung wird der gleiche iterative Algorithmus wie bei der Zeilen- und Arrayverarbeitung verwendet. Mehrere Serverprozesse können jedoch die Arbeit aufteilen und parallel ausführen.
Angenommen, die funktionale Anforderung ist mit den Beispielen für Zeilen und Arrays identisch. Die Hauptunterschiede sind:
-
Die Scanereignisdatensätze werden in einer Masse von Flat Files gespeichert.
-
32 Serverprozesse müssen parallel ausgeführt werden, wobei jeder Serverprozess eine andere externe Tabelle abfragt.
-
Sie verwenden PL/SQL, um die Parallelisierung zu erreichen, indem Sie 32 Threads desselben PL/SQL-Programms ausführen, wobei jeder Thread gleichzeitig als separater Job ausgeführt wird, der von Oracle Scheduler verwaltet wird. Ein Job ist die Kombination aus einem Zeitplan und einem Programm.
Der folgende PL/SQL-Code verwendet manuelle Parallellismus:
declare
sqlstmt varchar2(1024) := q'[
-- BEGIN embedded anonymous block
cursor c is select s.* from ext_scan_events_${thr} s;
type t is table of c%rowtype index by binary_integer;
a t;
rows binary_integer := 0;
begin
for r in (select ext_file_name from ext_scan_events_dets where ora_hash(file_seq_nbr,${thrs}) = ${thr})
loop
execute immediate
'alter table ext_scan_events_${thr} location' || '(' || r.ext_file_name || ')';
open c;
loop
fetch c bulk collect into a limit ${array_size};
exit when a.count = 0;
forall i in 1..a.count
insert into stage1_scan_events d values a(i);
commit;
-- demo instrumentation
rows := rows + a.count; if rows > 1e3 then exit when not sd_control.p_progress('loading','userdefined',rows); rows := 0; end if;
end loop;
close c;
end loop;
end;
-- END embedded anonymous block
]';
begin
sqlstmt := replace(sqlstmt, '${array_size}', to_char(array_size));
sqlstmt := replace(sqlstmt, '${thr}', thr);
sqlstmt := replace(sqlstmt, '${thrs}', thrs);
execute immediate sqlstmt;
end;
Die Funktion ORA_HASH teilt die Tabelle ext_scan_events_dets in 32 gleichmäßig verteilte Buckets auf. Anschließend ruft die SELECT-Anweisung die Dateinamen für den Bucket 0 ab. Für jeden Dateinamen im Bucket setzt das Programm den Speicherort der externen Tabelle auf diesen Dateinamen. Anschließend fragt das Programm die externe Tabelle mithilfe der Batch-Verarbeitung ab, fügt sie in die Staging-Tabelle ein, und schreibt sie fest.
Während Job 1 ausgeführt wird, werden die anderen 31 Oracle Scheduler-Jobs parallel ausgeführt. Auf diese Weise liest jeder Job gleichzeitig eine andere Teilmenge der Scanereignisdateien und fügt die Datensätze aus ihrer Teilmenge in dieselbe Staging-Tabelle ein.
Die manuelle Parallelisierungstechnik hat gegenüber den alternativen iterativen Techniken folgende Vorteile:
-
Bei großen Datasets ist die Performance wesentlich besser, da Serverprozesse parallel arbeiten.
-
Wenn die Anwendung ORA_HASH verwendet, um die Workload zu verteilen, kann jeder Ausführungsthread auf dieselbe Datenmenge zugreifen. Dies bedeutet, dass die parallelen Prozesse gleichzeitig beendet werden können.
Die manuelle Parallelisierungstechnik hat folgende Nachteile:
-
Der Code ist relativ langwierig, kompliziert und schwer verständlich.
-
Die Anwendung muss eine bestimmte Menge an Vorbereitungsarbeiten durchführen, bevor die Datenbank mit der Hauptarbeit beginnen kann, bei der die Zeilen parallel verarbeitet werden.
-
Wenn mehrere Threads dieselben Vorgänge für eine gemeinsame Gruppe von Datenbankobjekten ausführen, sind Sperr- und Latchkonflikte möglich.
-
Die parallele Verarbeitung verbraucht im Vergleich zu den konkurrierenden iterativen Techniken erhebliche CPU-Ressourcen.
Siehe auch: RWP #8: Set-Based Parallel Processing
Set-basierte Verarbeitung
Die setbasierte Verarbeitung ist eine SQL-Technik, die ein Dataset innerhalb der Datenbank verarbeitet.
In einem setbasierten Modell definiert die SQL-Anweisung das Ergebnis und ermöglicht es der Datenbank, die effizienteste Methode zu ermitteln, um es zu erhalten. Im Gegensatz dazu verwenden iterative Algorithmen bedingte Logik, um jede Zeile oder Gruppe von Zeilen aus der Datenbank in die Clientanwendung abzurufen, die Daten auf dem Client zu verarbeiten und dann die Daten zurück an die Datenbank zu senden. Set-basierte Verarbeitung eliminiert den Overhead von Netzwerk-Roundtrip und Datenbank-API, da die Daten die Datenbank nie verlassen.
Angenommen, es gelten dieselben funktionalen Anforderungen wie in den vorherigen Beispielen. Die folgenden SQL-Anweisungen erfüllen diese Anforderung mit einem setbasierten Algorithmus:
alter session enable parallel dml;
insert /*+ APPEND */ into stage1_scan_events d
select s.* from ext_scan_events s;
commit;
Da die INSERT-Anweisung eine Unterabfrage der Tabelle ext_scan_events enthält, liest und schreibt eine einzelne SQL-Anweisung alle Zeilen. Außerdem führt die Anwendung einen einzelnen COMMIT aus, nachdem die Datenbank alle Zeilen eingefügt hat. Iterative Anwendungen hingegen führen nach dem Einfügen jeder Zeile oder jeder Gruppe von Zeilen ein COMMIT aus.
Die satzbasierte Technik hat erhebliche Vorteile gegenüber iterativen Techniken:
-
Wie in Real-World Performance-Demonstrationen und -Klassen gezeigt, ist die Performance bei großen Datensätzen um Größenordnungen schneller. Es ist nicht ungewöhnlich, dass die Laufzeit eines Programms von mehreren Stunden auf mehrere Sekunden sinkt.
-
Ein Nebeneffekt der größeren Verarbeitungsgeschwindigkeit ist, dass DBAs lang andauernde und fehleranfällige Batchjobs eliminieren und Geschäftsprozesse in Echtzeit angleichen können.
-
Die Länge des Codes ist deutlich kürzer, kurz als zwei oder drei Zeilen Code, da SQL das Ergebnis definiert und nicht die Zugriffsmethode.
-
Im Gegensatz zur manuellen Parallelisierung ist parallele DML für die Performance optimiert, da die Datenbank und nicht die Anwendung die Prozesse verwalten.
-
Beim Verknüpfen von Datasets verwendet die Datenbank automatisch hocheffiziente Hash Joins anstelle von relativ ineffizienten Schleifen auf Anwendungsebene.
-
Der APPEND-Hint erzwingt eine Direct-Path-Last, was bedeutet, dass die Datenbank kein Redo und Undo erstellt, wodurch I/O- und CPU-Verschwendung vermieden wird.
Set-basierte Verarbeitung hat einige potenzielle Nachteile:
-
Die Techniken sind vielen Datenbankentwicklern unbekannt, daher können sie schwieriger sein.
-
Da sich ein setbasiertes Modell völlig von einem iterativen Modell unterscheidet, muss der Quellcode beim Ändern vollständig neu geschrieben werden.
Siehe auch: RWP #7 Set-Based Processing, RWP #8: Set-Based Parallel Processing, RWP #9: Set-Based Processing–Data Deduplication, RWP #10: Set-Based Processing–Data Transformations und RWP #11: Set-Based Processing–Data Aggregation