Création d'applications évolutives
Concevez vos applications pour utiliser les mêmes ressources, indépendamment des populations d'utilisateurs et des volumes de données, et non pour surcharger les ressources système.
À propos des applications évolutives
Une application évolutive peut traiter une charge de travail plus importante avec une augmentation proportionnelle de l'utilisation des ressources système.
Une application évolutive peut traiter une charge de travail plus importante avec une augmentation proportionnelle de l'utilisation des ressources système. Par exemple, si vous doublez sa charge globale, une application évolutive utilise deux fois plus de ressources système.
Une application indisponible épuise une ressource système. Par conséquent, si vous augmentez la charge de travail de l'application, aucun débit supplémentaire n'est possible. Les applications évolutives entraînent des débits fixes et des temps de réponse médiocres.
Exemples d'épuisement des ressources :
-
Épuisement du matériel
-
Balayages de table dans des transactions à volume élevé entraînant des pénuries inévitables d'entrée/sortie de disque
-
Demandes réseau excessives causant des goulots d'étranglement de réseau et de programmation
-
Affectation de mémoire entraînant une pagination et un échange
-
Allocation excessive des processus et des threads provoquant l'effritement du système d'exploitation
Concevez vos applications pour utiliser les mêmes ressources, indépendamment des populations d'utilisateurs et des volumes de données, et non pour surcharger les ressources système.
Utiliser des variables de liaison pour améliorer l'évolutivité
Les variables de liaison, utilisées correctement, vous permettent de développer des applications efficaces et évolutives.
Une variable de liaison est un paramètre fictif dans un énoncé SQL qui doit être remplacé par une valeur ou une adresse de valeur valide pour que l'énoncé puisse s'exécuter correctement. En utilisant des variables attachées, vous pouvez écrire une instruction SQL qui accepte les entrées ou les paramètres lors de l'exécution.
Tout comme un sous-programme peut avoir des paramètres, dont les valeurs sont fournies par l'appelant, une instruction SQL peut avoir des paramètres fictifs de variable attachée, dont les valeurs (appelées variables attachées) sont fournies au moment de l'exécution. Tout comme un sous-programme est compilé une fois, puis exécuté plusieurs fois avec des paramètres différents, une instruction SQL avec des paramètres fictifs de variable attachée est analysée une fois, puis analysée avec des variables attachées différentes.
Une analyse complète, qui inclut l'optimisation et la génération de la source de rangées, est une opération très gourmande en UC. Une analyse flexible, qui ignore l'optimisation et la génération du row source et passe directement à l'exécution, est généralement beaucoup plus rapide qu'une analyse complète du même énoncé. (Pour un aperçu du traitement SQL, qui inclut la différence entre une analyse complète et une analyse partielle, voir Concepts d'Oracle Database.)
Non seulement une analyse complète est une opération nécessitant beaucoup d'UC, mais il s'agit également d'une opération impossible à mettre à l'échelle, car elle ne peut pas être effectuée simultanément avec de nombreuses autres opérations. Pour plus d'informations sur l'accès simultané et l'évolutivité, voir "À propos de l'accès simultané et de l'évolutivité".
Exemple 8-1 affiche la différence de performance entre une interrogation sans variable de liaison et une interrogation sémantiquement équivalente avec une variable de liaison. Le premier est plus lent et utilise beaucoup plus de verrous (pour plus d'informations sur l'incidence des verrous sur l'évolutivité, voir). Pour collecter et afficher des statistiques de performance, l'exemple utilise l'outil Runstats, décrit dans "Comparaison des techniques de programmation avec Runstats".
Note :
-
Exemple 8-1 affiche le coût de performance pour un seul utilisateur. Au fur et à mesure de l'ajout d'utilisateurs, le coût augmente rapidement.
-
Le résultat de l'exemple 8-1 a été produit avec ce paramètre :
SET SERVEROUTPUT ON FORMAT TRUNCATED
Note :
-
L'utilisation de variables attachées au lieu de littéraux de type chaîne est le moyen le plus efficace de rendre votre code invulnérable aux attaques par injection SQL. Pour plus de détails, voir Informations de référence sur le langage PL/SQL pour Oracle Database.
-
Les variables de liaison réduisent parfois l'efficacité des systèmes d'entreposage de données. Comme la plupart des interrogations sont longues, l'optimiseur tente de produire le meilleur plan pour chaque interrogation plutôt que la meilleure interrogation générique. L'utilisation de variables attachées force parfois l'optimiseur à produire la meilleure interrogation générique. Pour plus d'informations sur l'amélioration du rendement des systèmes d'entreposage de données, consultez le guide Oracle Database Data Warehousing.
Bien que l'analyse partielle soit plus efficace que l'analyse complète, le coût de l'analyse partielle d'une instruction est souvent très élevé. Pour optimiser l'efficacité et l'évolutivité de votre application, réduisez les analyses. Le moyen le plus simple de réduire l'analyse consiste à utiliser du code PL/SQL.
Exemple 8-1 : La variable de liaison améliore les performances
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;
/
Le résultat est similaire au texte suivant :
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.
Utiliser PL/SQL pour améliorer l'évolutivité
Certaines fonctionnalités PL/SQL peuvent vous aider à améliorer l'évolutivité des applications.
Comment PL/SQL réduit l'analyse
Le langage PL/SQL, optimisé pour l'accès à la base de données, met en cache les instructions en mode silencieux. Dans le langage PL/SQL, lorsque vous fermez un curseur, celui-ci se ferme dans votre perspective, c'est-à-dire que vous ne pouvez pas l'utiliser lorsqu'un curseur ouvert est requis, mais le langage PL/SQL le maintient ouvert et met en cache son instruction.
Si vous utilisez de nouveau l'instruction mise en cache, PL/SQL utilise le même curseur, ce qui évite une analyse. (PL/SQL ferme les instructions mises en cache si nécessaire. Par exemple, si votre programme doit ouvrir un autre curseur mais que cela dépasse le paramètre init.ora de OPEN_CURSORS.)
Le langage PL/SQL ne peut mettre en cache que les instructions SQL qui ne peuvent pas être modifiées au moment de l'exécution.
À propos de la déclaration EXECUTE IMMEDIATE
L'instruction EXECUTE IMMEDIATE crée et exécute une instruction SQL dynamique en une seule opération.
La syntaxe de base de l'instruction EXECUTE IMMEDIATE est la suivante :
EXECUTE IMMEDIATE sql_statement
sql_statement est une chaîne qui représente une instruction SQL. Si sql_statement a la même valeur à chaque exécution de l'instruction EXECUTE IMMEDIATE, PL/SQL peut mettre en cache l'instruction EXECUTE IMMEDIATE. Si sql_statement peut être différent à chaque exécution de l'instruction EXECUTE IMMEDIATE, PL/SQL ne peut pas mettre en cache l'instruction EXECUTE IMMEDIATE.
Voir aussi :
-
Informations de référence sur le langage PL/SQL Oracle Database pour plus d'informations sur EXECUTE IMMEDIATE
À propos de OPEN FOR Statements
L'instruction OPEN FOR a la syntaxe de base suivante.
La syntaxe de base de l'instruction OPEN FOR est la suivante :
OPEN cursor_variable FOR query
Votre application peut ouvrir cursor_variable pour plusieurs requêtes différentes avant de la fermer. Comme PL/SQL ne peut pas déterminer le nombre d'interrogations différentes avant l'exécution, PL/SQL ne peut pas mettre en cache l'instruction OPEN FOR.
Si vous n'avez pas besoin d'utiliser une variable de curseur, utilisez un curseur déclaré, pour améliorer les performances et faciliter la programmation. Pour plus de détails, voir Guide de développement d'Oracle Database.
Voir aussi :
-
Informations de référence sur le langage PL/SQL pour Oracle Database pour plus d'informations sur OPEN FOR
À propos du paquetage DBMS_SQL
Le package DBMS_SQL est une API permettant de créer, d'exécuter et de décrire des instructions SQL dynamiques. Vous devez utiliser le package DBMS_SQL au lieu de l'instruction EXECUTE IMMEDIATE si le compilateur PL/SQL ne peut pas déterminer au moment de la compilation le nombre ou les types de variables hôte de sortie (sélectionner des éléments de liste) ou de variables attachées d'entrée.
Le package DBMS_SQL est une API permettant de créer, d'exécuter et de décrire des instructions SQL dynamiques. L'utilisation du package DBMS_SQL nécessite plus d'efforts que l'utilisation de l'instruction EXECUTE IMMEDIATE, mais vous devez utiliser le package DBMS_SQL si le compilateur PL/SQL ne peut pas déterminer au moment de la compilation le nombre ou les types de variables hôte de sortie (sélectionner des éléments de liste) ou de variables attachées d'entrée.
Voir aussi :
-
Informations de référence sur le langage PL/SQL pour Oracle Database pour plus d'informations sur l'utilisation du paquetage DBMS_SQL
-
Informations de référence sur les ensembles et les types PL/SQL pour Oracle Database pour des informations complètes sur l'ensemble DBMS_SQL
À propos des énoncés SQL en masse
Le langage SQL en masse réduit le nombre d'allers-retours entre PL/SQL et SQL, ce qui réduit le nombre de ressources.
Sans SQL en masse, vous extrayez une ligne à la fois de la base de données (SQL), la traitez (PL/SQL) et la retournez à la base de données (SQL). Avec le langage SQL en masse, vous extrayez un ensemble de lignes de la base de données, traitez l'ensemble de lignes, puis renvoyez l'ensemble à la base de données.
Oracle recommande d'utiliser l'énoncé SQL en masse lorsque vous extrayez plusieurs rangées de la base de données et les retournez à la base de données, comme dans Exemple 8-2. Vous n'avez pas besoin d'instructions SQL en masse si vous extrayez plusieurs lignes mais que vous ne les retournez pas. Par exemple :
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;
Exemple 8-2 effectue une boucle sur une table t avec un nom_objet de colonne, en extrayant des jeux de 100 rangées, en les traitant et en les retournant à la base de données. (Limiter l'instruction FETCH en masse à 100 lignes nécessite un curseur explicite.)
Exemple 8-3 effectue la même tâche que Exemple 8-2, sans SQL en masse.
Comme le montrent les rapports TKPROF pour Exemple 8-2 et Exemple 8-3, l'utilisation d'énoncés SQL en masse pour cette tâche utilise presque 50 % moins de temps UC :
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
Toutefois, l'utilisation d'énoncés SQL en masse pour cette tâche utilise plus de temps d'UC et plus de code que l'utilisation d'un seul énoncé SQL, comme le montre ce rapport TKPROF :
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
Exemple 8-2 : SQL en masse
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;
/
Exemple 8-3 : Sans SQL en masse
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;
Voir aussi :
-
Guide de développement d'Oracle Database pour obtenir un aperçu des énoncés SQL en masse
-
Guide de développement d'Oracle Database pour plus d'informations sur l'utilisation d'énoncés SQL en masse
-
Informations de référence sur le langage PL/SQL pour Oracle Database pour plus d'informations sur les énoncés SQL en masse
À propos des accès simultanés et de l'évolutivité
Devise est l'exécution simultanée de plusieurs transactions. Plus votre application gère les accès simultanés, plus elle est évolutive. Une application évolutive peut traiter une charge de travail plus importante avec une augmentation proportionnelle de l'utilisation des ressources système.
Devise est l'exécution simultanée de plusieurs transactions. Les relevés des transactions concurrentes peuvent mettre à jour les mêmes données. Plus votre application gère les accès simultanés, plus elle est évolutive. Une application évolutive peut traiter une charge de travail plus importante avec une augmentation proportionnelle de l'utilisation des ressources système. Par exemple, si vous doublez sa charge globale, une application évolutive utilise deux fois plus de ressources système.
Les transactions concurrentes doivent produire des résultats significatifs et cohérents. Par conséquent, une base de données multiutilisateur doit fournir les éléments suivants :
-
Accès simultané aux données, ce qui garantit que les utilisateurs peuvent accéder aux données en même temps.
-
Cohérence des données, qui garantit que chaque utilisateur voit une vue cohérente des données, y compris les modifications visibles provenant de ses propres transactions et des transactions validées d'autres utilisateurs
Oracle Database assure la cohérence des données à l'aide d'un modèle à versions multiples et de différents types de verrouillage et niveaux d'isolement des transactions. Pour un aperçu du mécanisme de verrouillage d'Oracle Database, voir Concepts relatifs à Oracle Database. Pour un aperçu des niveaux d'isolement des transactions Oracle Database, voir Concepts relatifs à Oracle Database.
Pour décrire le comportement cohérent des transactions exécutées simultanément, les chercheurs de base de données ont défini une catégorie d'isolement des transactions appelée sérialisable. Une transaction sérialisable fonctionne dans un environnement qui semble être une base de données à utilisateur unique. Les transactions sérialisables sont souhaitables dans des cas spécifiques, mais pour 99 % de la charge de travail, l'isolement validé en lecture est parfait.
Oracle Database offre des fonctionnalités qui améliorent la simultanéité d'accès et l'évolutivité, telles que les séquences, les verrous internes (latches), les lectures et les écritures non bloquantes et le code SQL partagé.
Voir aussi : Concepts d'Oracle Database pour plus d'informations sur la simultanéité et la cohérence des données
À propos des séquences et des accès simultanés
Les séquences éliminent la sérialisation, améliorant ainsi la simultanéité et l'évolutivité de votre application.
Une séquence est un objet de schéma à partir duquel plusieurs utilisateurs peuvent générer des entiers uniques, ce qui est très utile lorsque vous avez besoin de clés primaires uniques.
Sans séquences, des valeurs de clé primaire uniques doivent être produites par programmation. Un utilisateur obtient une nouvelle valeur de clé primaire en sélectionnant la valeur produite la plus récente et en l'incrémentant. Cette technique nécessite un verrouillage pendant la transaction et entraîne l'attente par plusieurs utilisateurs de la prochaine valeur de clé primaire, c'est-à-dire la sérialisation des transactions. Les séquences éliminent la sérialisation, améliorant ainsi la simultanéité et l'évolutivité de votre application.
Voir aussi :
-
Concepts d'Oracle Database pour plus d'informations sur l'accès simultané aux séquences
À propos des verrous et des accès simultanés
Une augmentation des verrous internes (latches) entraîne davantage d'attentes basées sur la simultanéité d'accès, et donc une diminution de l'évolutivité.
Un verrouillage est un mécanisme de sérialisation simple de bas niveau qui coordonne l'accès multiutilisateur aux structures de données partagées. Les verrous internes protègent les ressources de mémoire partagées contre la corruption lorsqu'elles sont accessibles par plusieurs processus.
Une augmentation des verrous internes (latches) entraîne davantage d'attentes basées sur la simultanéité d'accès, et donc une diminution de l'évolutivité. Si vous pouvez utiliser soit une approche qui s'exécute légèrement plus rapidement pendant le développement, soit une approche qui utilise moins de verrous internes, utilisez cette dernière.
Voir aussi :
-
Concepts d'Oracle Database pour plus d'informations sur les verrous internes
-
Concepts d'Oracle Database pour plus d'informations sur les mutex, qui sont similaires aux verrous internes pour les objets uniques
À propos des lectures et des écritures non bloquantes et des accès simultanés
Dans Oracle Database, les lectures et les écritures sans blocage permettent aux interrogations de s'exécuter simultanément avec des modifications des données qu'elles lisent, sans bloquer ni s'arrêter. Les lectures et écritures non bloquantes permettent à une session de lire des données alors qu'une autre session modifie ces données.
À propos du code SQL partagé et des accès simultanés
Oracle Database compile une instruction SQL dans un objet exécutable une seule fois, puis d'autres sessions peuvent réutiliser l'objet tant qu'il existe. Cette fonction Oracle Database, appelée SQL partagé, permet à la base de données de compiler et d'optimiser des énoncés SQL très gourmands en ressources une seule fois, au lieu de chaque fois qu'une session utilise le même énoncé SQL.
Voir aussi : Concepts relatifs à Oracle Database pour plus d'informations sur les énoncés SQL partagés
Limiter le nombre de sessions concurrentes
Plus vous avez de sessions simultanées, plus vous avez d'attentes basées sur la concurrence et plus votre temps de réponse est lent.
Si votre ordinateur comporte n coeurs d'UC, au plus n sessions peuvent être actives simultanément. Chaque session "concurrente" supplémentaire doit attendre qu'un cœur de processeur soit disponible avant de pouvoir devenir actif. Si certaines sessions en attente n'attendent que des E/S, l'augmentation du nombre de sessions concurrentes à un peu plus de n peut améliorer légèrement les performances d'exécution. Toutefois, une augmentation excessive du nombre de sessions simultanées réduira considérablement les performances d'exécution.
Le paramètre d'initialisation SESSIONS détermine le nombre maximal d'utilisateurs simultanés dans le système. Pour plus de détails, voir Informations de référence sur Oracle Database.
Voir aussi : http://www.youtube.com/watch?v=xNDnVOCdvQ0 pour une vidéo qui montre l'effet de la réduction du nombre de sessions concurrentes sur un ordinateur avec 12 coeurs d'UC, passant de milliers à 96
Comparaison des techniques de programmation avec Runstats
L'outil Runstats vous permet de comparer les performances de deux techniques de programmation pour voir lesquelles sont les meilleures.
À propos de Runstats
L'outil Runstats vous permet de comparer les performances de deux techniques de programmation pour voir lesquelles sont les meilleures.
Runstats mesure les valeurs suivantes :
-
Temps écoulé pour chaque technique en centièmes de secondes (hsec)
-
Temps écoulé pour la première technique en pourcentage de celui de la deuxième technique
-
Statistiques système pour les deux techniques (par exemple, les appels d'analyse)
-
Verrouillage pour les deux techniques
Parmi les mesures précédentes, la plus importante est le verrouillage (voir "À propos des verrous et des accès simultanés").
Voir aussi : Exemple 8-1, qui utilise Runstats
Définition des statistiques d'exécution
L'outil Runstats est implémenté en tant que package qui utilise une vue et une table temporaire.
Note : Pour l'étape 1 de la procédure suivante, vous avez besoin du privilège SELECT sur les vues dynamiques du rendement V$STATNAME, V$MYSTAT et V$LATCH. Si vous ne pouvez pas obtenir ce privilège, demandez à quelqu'un qui a le privilège de créer la vue à l'étape 1 et de vous accorder le privilège SELECT dessus.
Étapes de configuration de l'outil Runstats :
-
Créez la vue utilisée par Runstats :
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; -
Créez la table temporaire utilisée par Runstats :
DROP TABLE run_stats; CREATE GLOBAL TEMPORARY TABLE run_stats ( runid VARCHAR2(15), name VARCHAR2(80), value INT ) ON COMMIT PRESERVE ROWS; -
Créez cette spécification de package :
CREATE OR REPLACE PACKAGE runstats_pkg AS PROCEDURE rs_start; PROCEDURE rs_middle; PROCEDURE rs_stop( p_difference_threshold IN NUMBER DEFAULT 0 ); end; /Le paramètre
p_difference_thresholdcontrôle la quantité de statistiques et de données de verrouillage affichées par Runstats. Runstats affiche les données uniquement lorsque la différence pour les deux techniques est supérieure àp_difference_threshold. Par défaut, Runstats affiche toutes les données. -
Créez le corps du package suivant :
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; /
Voir aussi :
-
Informations de référence sur Oracle Database pour plus d'informations sur les vues dynamiques du rendement
Utilisation de Runstats
Cette rubrique présente la syntaxe d'utilisation de l'outil Runstats.
Pour utiliser Runstats pour comparer deux techniques de programmation, appelez les procédures runstats_pkg à partir d'un bloc anonyme, à l'aide de la syntaxe suivante :
[ 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;
/
Voir aussi : Exemple 8-1, qui utilise Runstats
Performances réelles et techniques de traitement des données
Une tâche courante dans les applications de base de données d'un environnement d'entrepôt de données consiste à interroger ou à modifier un jeu de données volumineux. Le problème pour les développeurs d'applications est de savoir comment atteindre des performances élevées lors du traitement de jeux de données volumineux.
Les techniques de traitement se divisent en deux catégories : itérative et basée sur des ensembles. Au fil des années de tests, le groupe Real-World Performance a découvert que les techniques de traitement basées sur un jeu de données offrent de meilleures performances pour les applications de base de données qui traitent des jeux de données volumineux.
Cette rubrique comprend les principaux sous-thèmes suivants :
À propos du traitement itératif des données
Dans le traitement itératif, les applications utilisent une logique conditionnelle pour effectuer une boucle dans un ensemble de lignes.
En général, bien que pas nécessairement, le traitement itératif utilise un modèle client/serveur comme suit :
-
Transférez un groupe de lignes du serveur de base de données vers l'application client.
-
Traitez le groupe dans l'application client.
-
Transférez le groupe traité vers le serveur de base de données.
Vous pouvez implémenter des algorithmes itératifs à l'aide de trois techniques principales : le traitement ligne par ligne, le traitement de tableau et le parallélisme manuel.
Traitement itératif : Rangée par rangée
Dans le traitement ligne par ligne, un seul traitement effectue une boucle dans un jeu de données et s'exécute sur une seule ligne à la fois. Dans une implémentation classique, l'application extrait chaque ligne de la base de données, la traite au niveau intermédiaire, puis renvoie la ligne à la base de données, qui exécute les opérations LMD et les validations (commits).
Supposons que votre besoin fonctionnel consiste à interroger une table externe nommée ext_scan_events, puis à insérer ses lignes dans une table intermédiaire organisée en "heaps" nommée stage1_scan_events. Le bloc PL/SQL suivant utilise une technique ligne par ligne pour répondre à cette exigence :
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;
La technique ligne par ligne présente les avantages suivants :
-
Il fonctionne bien sur les petits ensembles de données.
-
L'algorithme de boucle est familier à tous les développeurs professionnels, facile à écrire rapidement et facile à comprendre.
La technique ligne par ligne présente les inconvénients suivants :
-
Le temps de traitement peut être trop long pour les jeux de données volumineux.
-
L'application s'exécute en série et ne peut donc pas exploiter les fonctions de traitement parallèle natives d'Oracle Database exécutées sur du matériel moderne.
Voir aussi : Traitement par jeu RWP #7
Traitement itératif : Tableaux
Le traitement des tableaux est identique au traitement ligne par ligne, sauf qu'il traite un groupe de lignes dans chaque itération plutôt qu'une seule ligne.
Supposons que votre besoin fonctionnel soit le même que dans l'exemple X-X : interrogez une table externe nommée ext_scan_events, puis insérez ses lignes dans une table intermédiaire organisée en "heaps" nommée stage1_scan_events. Le bloc PL/SQL suivant utilise une technique de tableau pour répondre à cette exigence :
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;
Le code précédent diffère du code ligne par ligne équivalent en utilisant un opérateur BULK COLLECT dans FETCH STATEMENT, qui est limité par la valeur array_size de type PLS_INTEGER. Par exemple, si array_size est réglé à 100, l'application extrait des rangées dans des groupes de 100.
La technique du tableau présente les avantages suivants par rapport à la technique ligne par ligne :
-
Le tableau permet à l'application de traiter un groupe de lignes en même temps, ce qui signifie qu'il réduit les allers-retours réseau, le temps COMMIT et le chemin de code dans le client et le serveur.
-
La base de données est plus efficace car le processus serveur bat les insertions et les valide après chaque groupe d'insertions plutôt qu'après chaque insertion.
Les inconvénients de cette technique sont les mêmes que pour le traitement ligne par ligne. Le temps de traitement peut être inacceptable pour les jeux de données volumineux. De plus, l'application doit s'exécuter en série sur un seul coeur de processeur et ne peut donc pas exploiter le parallélisme natif d'Oracle Database.
Traitement itératif : Parallélisme manuel
Le parallélisme manuel utilise le même algorithme itératif que le traitement ligne par ligne et le traitement de tableau, mais permet à plusieurs processus serveur de diviser le travail et de s'exécuter en parallèle.
Supposons que l'exigence fonctionnelle soit la même que dans les exemples ligne par ligne et tableau. Les principales différences sont les suivantes :
-
Les enregistrements d'événements de numérisation sont stockés dans un grand nombre de fichiers plats.
-
32 processus serveur doivent être exécutés en parallèle, chaque processus serveur interrogeant une table externe différente.
-
Vous utilisez PL/SQL pour atteindre le parallélisme en exécutant 32 threads du même programme PL/SQL, chaque thread s'exécutant simultanément en tant que travail distinct géré par Oracle Scheduler. Un emploi est la combinaison d'un horaire et d'un programme.
Le code PL/SQL suivant utilise le parallélisme manuel :
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;
La fonction ORA_HASH divise la table ext_scan_events_dets en 32 seaux répartis uniformément, puis l'instruction SELECT extrait les noms de fichier du seau 0. Pour chaque nom de fichier dans le seau, le programme définit l'emplacement de la table externe à ce nom de fichier. Le programme utilise ensuite le traitement par lots pour interroger la table externe, l'insérer dans la table intermédiaire, puis valider.
Pendant l'exécution du travail 1, les 31 autres travaux Oracle Scheduler s'exécutent en parallèle. De cette façon, chaque travail lit simultanément un sous-ensemble différent des fichiers d'événements d'analyse et insère les enregistrements de son sous-ensemble dans la même table intermédiaire.
La technique de parallélisme manuel présente les avantages suivants par rapport aux techniques itératives alternatives :
-
Il fonctionne beaucoup mieux sur les jeux de données volumineux car les processus serveur fonctionnent en parallèle.
-
Lorsque l'application utilise ORA_HASH pour répartir la charge globale, chaque thread d'exécution peut accéder à la même quantité de données, ce qui signifie que les processus parallèles peuvent se terminer en même temps.
La technique du parallélisme manuel présente les inconvénients suivants :
-
Le code est relativement long, compliqué et difficile à comprendre.
-
L'application doit effectuer un certain nombre de travaux préparatoires avant que la base de données puisse commencer le travail principal, qui consiste à traiter les lignes en parallèle.
-
Si plusieurs threads effectuent les mêmes opérations sur un ensemble commun d'objets de base de données, des contentions de verrouillage et de verrouillage sont possibles.
-
Le traitement parallèle consomme des ressources CPU importantes par rapport aux techniques itératives concurrentes.
Voir aussi : RWP #8 : Traitement parallèle basé sur des ensembles
À propos du traitement par jeu
Le traitement par jeu de données est une technique SQL qui traite un jeu de données dans la base de données.
Dans un modèle basé sur un ensemble, l'instruction SQL définit le résultat et permet à la base de données de déterminer le moyen le plus efficace de l'obtenir. En revanche, les algorithmes itératifs utilisent une logique conditionnelle pour extraire chaque ligne ou groupe de lignes de la base de données vers l'application client, traiter les données sur le client, puis renvoyer les données à la base de données. Le traitement par jeu élimine la surcharge liée à l'aller-retour réseau et à l'API de base de données, car les données ne quittent jamais la base de données.
Supposons la même exigence fonctionnelle que dans les exemples précédents. Les instructions SQL suivantes répondent à cette exigence à l'aide d'un algorithme basé sur un ensemble :
alter session enable parallel dml;
insert /*+ APPEND */ into stage1_scan_events d
select s.* from ext_scan_events s;
commit;
Comme l'énoncé INSERT contient une sous-interrogation de la table ext_scan_events, un énoncé SQL unique lit et écrit toutes les rangées. De plus, l'application exécute un COMMIT unique après que la base de données a inséré toutes les rangées. En revanche, les applications itératives exécutent un COMMIT après l'insertion de chaque ligne ou de chaque groupe de lignes.
La technique basée sur un ensemble présente des avantages significatifs par rapport aux techniques itératives :
-
Comme le démontrent les démonstrations et les classes Real-World Performance, les performances sur les grands ensembles de données sont beaucoup plus rapides. Il n'est pas rare que le temps d'exécution d'un programme passe de plusieurs heures à plusieurs secondes.
-
Un effet secondaire de l'augmentation considérable de la vitesse de traitement est que les administrateurs de base de données peuvent éliminer les tâches par lots de longue durée et sujettes aux erreurs, et induire les processus d'affaires en temps réel.
-
La longueur du code est considérablement plus courte, soit deux ou trois lignes de code, car SQL définit le résultat et non la méthode d'accès.
-
Contrairement au parallélisme manuel, les opérations LMD en parallèle sont optimisées pour les performances car la base de données, plutôt que l'application, gère les processus.
-
Lors de la jointure de jeux de données, la base de données utilise automatiquement des jointures de hachage très efficaces au lieu de boucles de niveau application relativement inefficaces.
-
Le conseil APPEND force une charge par chemin direct, ce qui signifie que la base de données ne crée pas d'informations de journalisation et d'annulation, évitant ainsi le gaspillage d'E/S et de CPU.
Le traitement par jeu de tables présente certains inconvénients potentiels :
-
Les techniques ne sont pas familières à de nombreux développeurs de bases de données, elles peuvent donc être plus difficiles.
-
Comme un modèle basé sur un jeu est complètement différent d'un modèle itératif, sa modification nécessite une réécriture complète du code source.
Voir aussi : 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, et RWP #11 : Set-Based Processing–Data Aggregation