Paramètres
Transcription
Paramètres
ÉLÉMENTS AGISSANT SUR L'OPTIMISATION Les principaux problèmes à prendre en compte dans les processus de tuning peuvent être scindés en trois catégories : La première étape du tuning consiste à identifier le problème. Éliminer les points qui ne posent pas problème est aussi important que d'identifier les zones où se posent les problèmes. L'OS devrait être la première zone vérifiée. Ensuite, le prochain niveau à vérifier est l'application. Enfin, s'intéresser à l'instance. On s'intéressera également, nécessairement à la partie optimisation SQL et aux bons choix fait vis-à-vis de l'optimisation. Au niveau OS Mémoire, CPU, et I/O (disk et réseau). Vérifier par vmstat, top, etc. Un bon repère est la mesure du taux de pagination (plus de demandes de mémoire que de mémoire disponible. Avec comme solutions, redimensionnement de la RAM, déplacer des process sur d'autres machines, limiter la taille mémoire qu'un process peut utiliser. On peut aussi agir en écrivant des ordres SQL plus efficients. Une autre solution est le pool de connexions. La demande de ressources CPU est mis en évidence par les files d'attentes. Tous les OS ont un scheduler qui détermine quand un process vit et combien de temps il vit. Si le problème réside dans les I/O (longues files d'attente ou des taux élevés de requêtes), on peut envisager des solutions telles data stripping ou ajout de disques. On peut aussi vérifier l'adéquation entre les caractéristiques de l'OS et les paramètres globaux choisis pour l'instance (ex : db_file_multi_block_read_count). Au niveau de l'application Serialisation des ressources La sérialisation des ressources peut provenir de nombreux cas de figure. Par exemple, une activité qui nécessite que tous les chèques doivent avoir des identifiants consécutifs et de progression continue (pas de "trous"). Cette contrainte ne peut être gérée que par une source unique de valeurs, ie les séquences. ex : 1 sequence -uniques + consécutif, pour plusieurs process Schéma relationnel ! à la sur-normalisation qui provoque trop de jointures et à la sous normalisation qui provoque des contraintes supplémentaires Gestion des sessions ex : une application web qui nécessite connexion et déconnexions continuelles Curseurs et design du programme Les deux requêtes : SELECT * FROM hr.employees WHERE employee_id = 7900; et SELECT * FROM hr.employees WHERE employee_id = 7369; n'utiliseront qu'un seul curseur s'il existe un index sur employee_id même si le texte SQL est différent et que le paramètre CURSOR_SHARING = FORCE 1/15 Statistiques pour la SGA Diagnostic Tools for Tuning the Shared Pool The V$SGASTAT or V$SGAINFO view displays the sizes of all SGA structures. V$SGASTAT is more detailed for the various pieces of the shared pool. V$SGAINFO provides a summary including the granule size and whether the memory area is resizable. The contents of the shared pool are not aged out as long as free memory is available in the shared pool. Use the following dynamic views to diagnose performance issues related to the library cache: V$LIBRARYCACHE: Statistics on library cache management V$LIBRARY_CACHE_MEMORY: Statistics on memory use for each of the namespaces V$SQLAREA: Full statistics about all shared cursors and the first 1,000 characters of the SQL statement V$SQL: Statistics about shared SQL area; contains one row for each child of the original SQL text entered. The V$SQL view is a similar view to V$SQLAREA, except that it does not include a GROUP BY clause that can make the V$SQLAREA view expensive to query. A SELECT of V$SQL is a latched operation. V$SQLSTATS: A low impact view for use instead of the other V$SQL* views. V$SQLSTATS uses a mutex rather than a latch. V$SQLTEXT: The full SQL text without truncation, in multiple rows V$DB_OBJECT_CACHE: Database objects cached, including packages; also objects such as tables and synonyms, where these are referenced in SQL statements The Statspack and AWR reports contain information from these views and also contain information about SQL statements found in the library cache when either of the snapshots was taken. Les performance sont impactées lorsque l'on a des « contention » pour une ou plusieurs ressources. Il y a typiquement les ressources hardware (par exemple CPU, memoire, réseau, et disque) et software ou les ressources applicatives (row lock, verrous, etc). 2/15 LES INDEX Optimisations réalisées automatiquement • • Utilisation des ordres DISTINCT : dans ce cas, Oracle trie les lignes, écarte les valeurs dupliquées dès qu'elles sont rencontrées. Les index ne sont pas utilisés pour traiter cette clause Utilisation des clauses GROUP BY : même remarques que pour les clauses DISTINCT Critères de choix pour les index Si la table doit subir beaucoup d'INSERT créer l'index après le remplissage sauf pour des cas où l'index sert à vérifier l'unicité des valeurs S'il est utilisé par un cluster, le créer avant toute insertion Les colonnes de type LONG et LONG RAW ne peuvent être indexées Limiter le nombre d'index (3 est une bonne moyenne maximale) Affecter éventuellement des tablespace ad hoc. Eviter de choisir des colonnes à indexer avec peu de dispersion (contenant des valeurs à peu de dispersion comme oui/non, …) Consulter le Administrator's Guide pour les calculs de la taille et des paramètres de stockage de l'index. Ecriture d'ordres SQL tirant parti des index Un index peut être utilisé si : il est référencé dans un prédicat la colonne indexée n'est pas modifiée par une fonction ou une opération arithmétique. Un index sera utilisé si : l'optimiseur de requêtes décide que son usage est approprié. Un index ne sera pas utilisé si : il n'y a pas de clause WHERE le prédicat modifie la colonne d'une façon quelconque la recherche se fait explicitement pour des lignes comportant des valeurs NULL ou NOT NULL dans la colonne indexée (le prédicat contient les opérateurs IS / IS NOT NULL). Si la colonne doit être modifiée, l'index ne sera pas utilisé, comme par exemple, dans : where SAL * 12 = 24000 where SAL + 0 = 500 where ' ' | | ENAME = ' Smith' Une exception, cependant, existe à cette règle : une optimisation spéciale en vue de trouver la plus petite ou plus grande valeur dans la tableautorise le SELECT à comporter une expression contenant { MIN | MAX } (col { + | - } constante ) sans aucune autre colonne, comme dans : Select 2 * MAX (SAL+1) from emp; Création d'index efficaces. Les index concaténés peuvent être d'un grand intérêt pour l'amélioration de l'efficacité des requêtes. Supposons qu'un index ait été créé au moyen de la commande : Create index Ind_Nom_Ville_Etat on Employee (Last_Name, City, State) Lorsque la clause Where utilise les mêmes colonnes que celles de l'index, celui-ci sera utilisé dans la requête : Select ... from employee where Last_Name = 'Smith' and city = 'Paris' and state = 'MD' Si seulement la première colonne de l'index est citée dans la clause where, l'index sera encore utilisé, mais il risque d'être moins sélectif que dans l'exemple précédent : Select ... where Last_Name = 'Jones' Si dans la clause where n'est pas citée la première colonne, l'index ne sera pas utilisé. ainsi : Select ... where city = 'Paris' 3/15 and state = 'MD' Une requête donnée peut être formulée de façons différentes pour chercher ou éviter d'utiliser un index. Ainsi, en supposant que la colonne Hiredate est de type date, et possède un index non-unique, la requête : Select * from emp where to_char(Hiredate, 'Month dd, yyyy') = 'January 14, 1992') modifie la colonne Hiredate, et l'index ne sera pas utilisé, alors que dans la requête : Select * from emp where Hiredate = to_date('January 14, 1992','month dd, yyyy') la constante (January 14, 1992) sera convertie en date, et Oracle utilisera l'index. La requête suivante utilisera également l'index : Select * from emp where Hiredate = '14-JAN-92' Sélectivité d'un index Généralités La sélectivité est l'un des paramètres fondamentaux à prendre en compte lors de la création des index quant à leur efficacité et leur bien fondé. La séléctivité se calcule comme ceci: Ratio entre le nombre de valeurs distinctes sur le nombre de lignes de la table. Si une table a 1000 lignes et une colonne indexée de cette table, 950 valeurs distinctes, alors la sélectivité de cet index sera de 950/1000, soit 0.95. La meilleure sélectivité d'un index sera de 1.0 (cas des index uniques). Ce critère est important. Prenons le cas d'un index de 1000 lignes avec une sélectivité de 0.005. Cela signifiera que pour chaque valeur distincte de l'index, 200 lignes seront ramenées en moyenne. Dans ce cas, il est probable qu'un balayage complet de la table sera plus efficace. Un index ayant une sélectivité se rapprochant de 1, sera le plus efficace. On considère qu'un index commence à être bon, lorsque sa sélectivité arrive à 0.75. Mesure manuelle de la sélectivité Soit une colonne ename, à indexer. La requête suivante permet de connaître le nombre de valeurs distinctes sur la colonne : Select count (distinct ename) from emp; Puis : Select count(*) from emp; permet de connaître le nombre total de lignes de la table. L'intérêt de la méthode manuelle est qu'elle permet d'évaluer l'intérêt d'un index avant sa création. Utilisation de la commande analyze. Lors de l'analyse d'une table, tous les index qui lui sont attachés, sont analysés également. Ainsi : Analyze table EMP compute statistics; La requête suivante donne le nombre de clés distinctes pour notre index : select DISTINCT_KEYS from USER_INDEXES where TABLE_NAME = 'EMP' and INDEX_NAME = 'EMP$ENAME' Le nombre total de lignes de la table est dans USER_TABLES. select NUM_ROWS from USER_TABLES where TABLE_NAME = 'EMP'; 4/15 TABLESPACES TEMPORAIRES Pour nombre d'opérations de tri (order, distinct, group by, …), Oracle utilise des segments temporaires. Il est souvent intéressant de réserver un tablespace spécifique à ce type d'opérations pour soulager le système. A cette fin, le meilleur choix consiste à créer et réserver un tablespace à de telles opérations "temporaires". Pour désigner un tablespace à ce type de fonctionnement, on peut y parvenir par : create/alter tablespace TEMP temporary; et en finir par : create/alter tablespace TEMP permanent; Principales précautions : initial et next identiques, pctincrease =0 Pour les grosses opérations de tri, choisir de grandes tailles de segments Alter tablespace TEMP coalesce (pctincrease=0) utiliser la vue DBA_SEGMENT CLUSTERS ET PROXIMITÉ PHYSIQUE DES DONNÉES La proximité des lignes en fonction d'un ordonnancement très utilisé sur telle ou telle colonne peut avoir un impact déterminant sur les performances des requêtes sur de grandes tables. On peut songer dans ce cas à les organiser en clusters. Malheureusement, les clusters offrent des performances très mauvaises en transactionnel : 6 à 8 fois plus lentes que des tables non clusterisées et génèrent des quantités d'informations bien plus importantes sur les aspects rollback et redo logs. Dans l'idéal, on peut penser à réordonner les lignes d'une table en créant une seconde table, via une commande CREATE AS SELECT, ou INSERT AS SELECT. Par malchance, ces deux commandes ne supportent pas la clause order by. Pour contourner cette difficulté, en supposant que sur notre table des employés EMP, la plupart des requêtes se fasse sur la colonne ENAME, créer une vue : create or replace view EMP_VIEW as selectENAME, JOB, EMPNO, MGR, SAL, COMM, HIREDATE, DEPTNO from EMP group by ENAME, JOB, EMPNO, MGR, SAL, COMM, HIREDATE, DEPTNO, ROWNUM; La clause group by forcera l'opération de tri et les lignes selectionnées par l'appel à la vue seront triées comme prévu sur le champ ENAME. L'ajout du champ rownum permet d'éviter l'élimination des doublons par leur regroupement. Ensuite il ne reste plus qu'à créer effectivement une nouvelle table à partir de la vue : create table EMP_TRIE as select * from EMP_view; 5/15 L'OPTIMISEUR : RBO, CBO ET HINTS L'optimiseur peut fonctionner sur deux modes : - Heuristique (Rule Based Optimizer – RBO) - Statistique (Cost Based Optimizer – CBO) Le choix se fait à partir du fichier init.ora en fonction du paramètre OPTIMIZER_MODE = {RULE | CHOOSE | FIRST_ROWS | ALL_ROWS} (Voir la doc "Reference" d'Oracle, section Initialization parameters). Le résultat de l'optimisation est un plan d'exécution, ie une séquence d'opérations à exécuter. A la suite de quoi, il reste à appliquer les techniques d'accès appropriées pour chaque opération du plan d'exécution, et gérer les flots de données entre chaque opération. Le résultat du plan d'exécution est un ensemble d'opérations de niveau intermédiaire, dit algèbre physique constituée : - De chemins d'accès aux données - D'opérations manipulant les données (correspondant aux noeuds internes de l'arbre de requête). Beaucoup d'infos supplémentaires dans la doc "Tuning" d'Oracle. Etapes à suivre 1/ il faut traduire une requête exprimée en langage déclaratif en une suite d'opérations (similarité avec les opérateurs de l'algèbre relationnelle) 2/ En fonction des coûts, il faudra estimer la meilleure stratégie. 3/ On obtient le plan d'exécution. Il n'y a plus qu'à le traiter au niveau physique. Le mode Rule based optimizer Tableau des chemins d'accès disponibles en mode RBO Path 1: Single Row by Rowid Path 2: Single Row by Cluster Join Path 3: Single Row by Hash Cluster Key with Unique or Primary Key Path 4: Single Row by Unique or Primary Key Path 5: Clustered Join Path 6: Hash Cluster Key Path 7: Indexed Cluster Key Path 8: Composite Index Path 9: Single-Column Indexes Path 10: Bounded Range Search on Indexed Columns Path 11: Unbounded Range Search on Indexed Columns Path 12: Sort-Merge Join Path 13: MAX or MIN of Indexed Column Path 14: ORDER BY on Indexed Column Path 15: Full Table Scan 6/15 Le mode Cost based optimizer Basé sur les statistiques et en particulier sur la commande analyze : exec DBMS_UTILITY.ANALYZE_SCHEMA('APP_OWNER','COMPUTE'); si on veut passer du mode CBO en mode RBO : ANALYZE table SALES delete statistics; Lors de la re-génération de statistiques il n'est pas nécessaire de supprimer auparavant les précédentes statistiques. On peut définir le mode de fonctionnement par défaut de l'optimiseur en fixant la valeur du paramètre optimizer_mode du fichier init*.ora Si Oracle dispose de statistiques, il utilisera par défaut le mode CBO. Si ces statistiques sont supprimées (par la commande analyze ... delete statistics) il se rabattra automatiquement sur le mode RBO. Plusieurs tables sont impliquées, dont DBA_TABLES, plusieurs tables V$. Pourquoi utiliser des hints Il peut être nécessaire d'utiliser les hints parce que l'optimiseur est un programme, prévu pour tenir compte d'un certain nombre fini, de paramètres. Il est clair que dans le monde des bases de données, la quantité de paramètres pouvant influer de façon significative sur les performances est extrêmement variable en nombre, en qualité et en valeur, sans parler de la dépendance envers le contexte. Il est donc probable que aussi raffinés qu'ils pourront être, les algorithmes mis en oeuvre par cet optimiseur présenteront ponctuellement mais à coup sûr des points faibles, des imperfections. Les hints sont des ordres que l'on peut glisser dans une requête SQL, qui seront interprétés prioritairement par l'optimiseur, afin d'orienter ou redéfinir un plan d'exécution décidé par le rédacteur (averti) de la requête. Ci-dessous, on peut lire un exemple de requête utilisant un hint : select /*+ USE_HASH(COMPANY) CACHE(COMPANY) */ company.name, sum(sales.dollar_amount) from company, sales -- company est une petite table et sales est grande where company.company_id=sales.company_id group by company.name; 7/15 EXPLAIN PLAN - OPÉRATIONS DE LA BASE Génération de plan L'ordre général des opérations est le suivant : - Créer la table qui réceptionnera le plan. Oracle fournit un script qui réalise cette création : $ORACLE_HOME/rdbms/admin/utlxplan.sql - Génération du plan par la commande explain plan - Requête sur la table plan_table àplacer éventuellement dans un script SQL : REM montre le plan et l'efface de la table plan_table REM set TRUNCATE on set PAGESIZE 128 COL OPERATION_TREE FORMAT A30 COL OPTIONS FORMAT A24 COL OBJECT_NAME FORMAT A24 SELECT LPAD(' ',2*LEVEL)||OPERATION OPERATION_TREE,OPTIONS,OBJECT_NAME FROM PLAN_TABLE CONNECT BY PRIOR ID = PARENT_ID START WITH ID = 1 ORDER BY ID ; delete from PLAN_TABLE; Opérations utilisées dans la base Les opérations utilisées dans la base sont de deux types : opérations d'ensembles ou bien de lignes. Les différences essentielles sont : Row operations Set operations Exécutées sur une ligne à la fois Exécutées sur un ensemble de lignes résultant Exécutées au niveau du FETCH Exécutée au niveau de l'EXECUTE, ie à l'ouverture du curseur Les premiers résultats arrivent avant la fin de l'exécution Les résultats arrivent après la fin de l'exécution Ex : un full table scan Ex : un full table scan avec une clause de group by Ci-après, quelques exemples d'opérations les plus courantes sont cités, accompagnées de quelques commentaires. Pour une lecture exhaustive, se référer à la documentation Oracle (Tuning en particulier). Opération Set ou row Usage AND-EQUAL r Quand deux colonnes ou plus avec un index monocolonne non-unique sont impliquées BITMAP r Quand des index bitmap sont utilisés HASH JOIN s Dans une opération de jointure quand un ou plusieurs résultats intermédiaires risquent d'être très grands. INDEX FULL SCAN r Pour des opérations sur des index monocolonnes INDEX RANGE SCAN r Quand sont impliqués des opérateurs tels between, like, etc INDEX UNIQUE SCAN r Pour un test d'égalité sur toutes les colonnes d'un index unique MERGE JOIN r Pour les opérations de jointure sur des tables dépourvues d'index sur les colonnes utilisées dans la condition de jointure NESTED LOOPS r Très communément utilisé dans des jointures impliquant dans les conditions de jointure au moins une colonne indexée. REMOTE r/s Présence de database links 8/15 Opération Set ou row Usage SORT JOIN s Sous produit de l'opération MERGE JOIN TABLE ACCESS BY ROWID r Accès à une table via un index. Nommée aussi "user rowid" TABLE ACCESS FULL r Pour accéder à une table lorsqu'on ne peut pas appliquer de conditions sur des colonnes indexées Exemples de plans Pour les opérations les plus fréquentes, quelques exemples de requêtes, leur plan et leur interprétation. HASH JOIN select compagnie.nom from compagnie,ventes where compagnie.compagnie_id=ventes.compagnie_id and ventes.period_id = 3 and ventes.ventes_total>1000; Le plan : HASH JOIN TABLE ACCESS FULL VENTES TABLE ACCESS FULL COMPAGNIE Si l'une des tables est notablement plus petite que l'autre et qu'elle tient en mémoire, l'optimiseur utilisera cette opération plutôt qu'une jointure NESTED LOOP. Même si un index est disponible une telle opération peut s'avérer préférable à une jointure NESTED LOOP. MERGE JOIN select compagnie.name from compagnie, ventes where compagnie.compagnie_id+0=ventes.compagnie_id+0 and ventes.period_id = 3 and ventes.ventes_total>1000; Le plan : MERGE JOIN SORT JOIN TABLE ACCESS FULL VENTES SORT JOIN TABLE ACCESS FULL COMPAGNIE Les index sont neutralisés et ne peuvent donc être utilisés. Oracle fait un full scan (TABLE ACCESS FULL) sur chaque table, trie le résultat (SORT JOIN) et fusionne le résultat. TABLE ACCESS BY ROWID select nom from compagnie where compagnie_id = 12345 and active_flag = 'O'; Le plan : TABLE ACCESS BY ROWID COMPAGNIE INDEX UNIQUE SCAN COMPAGNIE_PK L'utilisation de la colonne compagnie_id dans la clause where de la requête autorise l'usage de l'index COMPAGNIE_PK. Si cet index ne contient pas également la colonne nom, oracle devra pour y accéder utiliser la valeur du rowid retournée par l'index. Si l'index a été créé en concaténant compagnie_id et nom la valeur du nom sera retournée directement. 9/15 Interpréter un plan L'interprétation se fait de l'intérieur vers l'extérieur et du haut vers le bas. Les premières opérations s'exécutant seront situées vers les "feuilles" de l'arbre dont la racine sera en haut à gauche. Exemple de plan : 10 PROJECTION 9 SORT UNIQUE 8 UNION-ALL 5 MERGE JOIN 2 SORT JOIN 1 TABLE ACCESS FULL COMPANY 4 SORT JOIN 3 TABLE ACCESS FULL SALES 7 TABLE ACCESS BY ROWID COMPETITOR 6 INDEX UNIQUE SCAN COMPETITOR_PK Les chiffres à gauches sont donnés à titre indicatif de l'ordre dans lequel se déroulent les opérations. 10/15 CONSEILS POUR L'OPTIMISATION Eviter les full scan non désirés Il y a deux raisons majeures d'éviter les full scans (balayages complets -séquentiels, des tables) : - Ils ne sont pas sélectifs - Les données lues via des full scans, sont rapidement supprimées de la SGA Quand les full scans sont-ils utilisés : - pas d'index sur la table - pas de conditions (ie pas de clause where) - il y a des conditions utilisées dans la requête, mais correspondant aux cas prévus de non utilisation des index par l'optimiseur – voir paragraphe sur les index, ci-dessus. Utiliser seulement des index sélectifs Voir ci-dessus aspects généraux sur la gestion des index Gérer les jointures multitables Oracle fournit trois opérations de jointure : nested loops ie boucles imbriquées, merge join ie tri fusion, et hash join – jointures hash. Merge join -tri fusion Cette opération requiert les étapes suivantes : - Accès full table de chaque table de la jointure - Tri des résultats (sort join) - fusion des résultats triés (merge join des résultats du sort join) Cet algorithme est utilisé lorsque les index sont absents ou inexploitables. Il travaille sur des ensembles de données (lignes). Il ne retourne pas de lignes tant que tout le processus n'est pas terminé. Les résultats ne seront pas disponibles longtemps dans la SGA. Il peut être nécessaire d'allouer des segments temporaires Conséquences sur l'optimisation: Les merge joins seront efficaces : - là où les full scans seront efficaces, ie lorsqu'ils seront préférables à l'utilisation d'index, - petites tables - voir "Gérer les très grandes tables" ci-après. - Durant l'opération de tri, oracle utilise autant que faire se peut une zone de la mémoire appellée sort area. La taille de cette zone est contrôlée par le paramètre sort_area_size. On peut améliorer les performances de cette étape en utilisant aussi les sort_direct_writes (voir ci-dessus tableau paramètres). - Les deux premières étapes sont les plus coûteuses en terme de performances. - Mettre à disposition un tablespace spécifique de type temporaire (create ou alter) : alter tablespace TEMP_1 temporary; Boucles imbriquées - Nested loops C'est le moyen le plus couramment utilisé pour les jointures. cette opération retourne très rapidement les premiers résultats. Utilisée s'il y a un index sur une table. La première opération effectuée par l'optimiseur est le choix de la table directrice. Cela peut être un full scan. Pour chaque ligne de la table directrice, un accès par index est réalisé sur la table secondaire. Si une correspondance existe, la ligne est retournée par l'opération nested loop. Par exemple, considérons la requête suivante : Select societe.nom from societe, ventes where societe.ste_id=ventes.ste_id and period=2; 11/15 On suppose que societe.ste_id est une cle primaire de la table societe et la colonne ste_id pour la table ventes. L'optimiseur pourra utiliser l'index en se basant sur la condition de jointure. Le plan d'exécution est : NESTED LOOPS TABLE ACCESS FULL VENTES TABLE ACCESS BY ROWID SOCIETE INDEX UNIQUE SCAN SOCIETE_PK La première étape de l'exécution sera le full scan sur ventes. Pour chaque enregistrement de la table ventes, l'index de SOCIETE_PK sera utilisé pour rechercher une correspondance. Les étapes nécessaires à l'exécution de cette commande : - Full table scan - Parcours indexé à la recherche de valeurs uniques sur la table secondaire - Si une correspondance est trouvée, accès par rowid à la ligne dans la table secondaire. Conséquences sur l'optimisation: La clé des performances des opérations en nested loops tient principalement à l'ordre dans lequel les tables sont jointes. La sélection de la table directrice est tout à fait critique. Si la jointure implique plus de deux tables, ce choix est encore plus critique. Le temps mis pour exécuter une jointure dans ces conditions, peut croître de façon exponentielle. Dans l'exemple précédent, la table ventes était la table directrice. Les points importants qu'il faut noter sur cet aspect : - Bien que les colonnes de la table ventes citées dans la condition de jointure soient indexées (clé primaire), l'index n'a pas été utilisé. Ceci parce la colonne indexée de la table ventes citée, n'a été utilisée que comme condition de jointure et non comme restriction dans la clause where. - L'optimiseur aurait tout aussi bien pu choisir la table societe comme table directrice et elle aurait subi le full scan plutôt que la table ventes. - En mode RBO, la table directrice eut été la dernière citée dans la clause from. - En mode CBO l'optimiseur tiendra compte de la taille des tables, et de la sétectivité des index avant de choisir une table directrice. Comment influer sur le "join path" : - En mode CBO on peut utiliser les hints. Les hints susceptibles d'influer sur le mode d'exécution d'une jointure sont : ORDERED jointure les tables en s'appuyant sur leur ordre dans la clause from INDEX lister un index spécifique à utiliser FULL indiquer une table à lire en full scan, ce qui peut en faire implicitement la table directrice USE_NL liste les tables à joindre en nested loop. - on peut anihiler l'utilisation d'un index (voir ci-dessus les modalités de mise en oeuvre des index) Jointures hash – hash join L'optimiseur peut décider dynamiquement de traiter une requête par une opération HASH JOIN en lieu et place d'un NESTED LOOP ou d'un MERGE JOIN. Si tel est le cas sur la même requête que dans l'exemple précédent : Select societe.nom from societe, ventes where societe.ste_id=ventes.ste_id and period=2; le plan d'exécution devient : HASH JOIN TABLE ACCESS FULL VENTES TABLE ACCESS FULL SOCIETE NB: Il ne faut pas confondre l'opération hash join avec les hash clusters qui sont une façon de stocker les données. Les hash join renvoient à la façon dont Oracle exécute une jointure. Durant une HASH JOIN deux tables sont jointes sur la base d'un ensemble de conditions de jointure telle celle de notre requête : where societe.ste_id=ventes.ste_id Durant la jointure, une des tables impliquées (ici ventes) est lue via un full table scan et une image (bitmap) des valeurs uniques des colonnes jointes de la table est construite en mémoire. Durant leur lecture, les lignes sont séparées en groupes ou partitions en se basant sur une fonction de hash qui permet de mapper (faire correspondre) les valeurs de jointure à la partition. Le hashage au niveau partition permet de séparer les deux tables en ensembles correspondants aux critères utilisés par la jointure. Ce type de hashing permet de traiter des jointures de grandes tables en limitant des pénalités trops lourdes en E/S. En plus de partitionner les tables, l'optimiseur construit une table de hashage en mémoire pour toutes les partitions de la table qui tiennent en mémoire. C'est une seconde fonction de hash qui est appliquée sur les valeurs de 12/15 jointure. l'image des valeurs créées sont ensuite utilisées comme filtre pour traiter les lignes de la deuxième table (SOCIETE). Si une ligne est repérée comme étant présente dans le bitmap, l'algorithme de hash est utilisé pour rechercher les données. Si les deux tables sont importantes, cet algorithme est également utilisé. Oracle met toujours en oeuvre deux niveaux de hashage, ce qui a pour conséquence d'importantes économies d'E/S. Plusieurs paramètres d'initialisation d'Oracle concernent la gestion des hash join : HASH_JOIN_ENABLED (v8) Doit être positionné à TRUE pour que l'optimiseur utilise ce mécanisme HASH_AREA_SIZE Fixe la taille de la zone de tri utilisée pour cette opération. Par défaut, sa valeur est double de celle du paramètre SORT_AREA_SIZE HASH_MULTIBLOCK_IO_COUNT Fixe le nombre de blocs à lire en une fois pour les opérations d'E/S de cette opération. Par défaut, égale à DB_FILE_MULTIBLOCK_READ_COUNT Sur le plan des ressources, il faut noter qu'il est indispensable de disposer de ressources matérielle (CPU et RAM) de très bon niveau pour mettre en oeuvre cette fonctionnalité. Ces types de jointures sont avantageuses lorsque les conditions suivantes sont réunies : - La jointure est exécutée dans le cadre d'application batch (peu ou pas d'accès concurrents) - La jointure est réalisée entre deux grandes tables (significativement plus importantes que le buffer cache). - La jointure se fait entre une grande et une petite table. Dans ce cas, il est intéressant de forcer la mise en cache de la petite table, en utilisant un hint : select /*+ USE_HASH(COMPANY) CACHE(COMPANY) */ company.name, sum(sales.dollar_amount) from company, sales -- company est une petite table et sales est grande where company.company_id=sales.company_id group by company.name; Dans cette requête, le hash join sera plus efficace que l'opération NESTED LOOP, car l'accès à la table company ne sera fait qu'une fois là où il aurait été fait X fois dans le cas du NESTED LOOP. La table sales n'est pas en cache car elle est de toute façon trop grosse pour tenir dans le buffer cache. Eviter les appels de fonction PL/SQL Gérer l'usage des bind variables Les éviter si possible en utilisant des valeurs en dur ou des variables. Revoir les process d'optimisation Par des mesures de statistiques. Se focaliser sur les requêtes SQL "offensives" (ie gourmandes). Histogrammes Ils sont utilisés pour améliorer la vision réelle de la distribution des valeurs d'une colonne. Ils permettent une meilleure estimation de la sélectivité d'un index. Soit par exemple une table qui a une ligne pour chaque personne sur terre. La table peut avoir deux colonnes, une pour le nom du pays et l'autre pour le nom des personnes, telle que : Create table population (pays varchar2(200), nom varchar2(200)); On trouvera 250 pays dans le monde et 6 milliards d'habitants ce qui fait une moyenne de 24 millions d'habitants par pays. Pour accélérer les requêtes on crée l'index suivant : create index population$pays on population(pays); 13/15 La sélectivité de cet index sera de 0,0000000416 ce qui devrait le disqualifier dans tous les cas. Cependant cet index pourrait être utile pour les requêtes concernant les pays à faible populations. Pour les pays à forte population il sera probablement plus efficace de faire un full scan plutôt que d'utiliser l'index sur une requête du type intervalle indexé. Pour les autres valeurs l'index pourrait bien être utile. Oracle utilise des histogrammes pour déterminer la distribution des données. Plus précisément, des histogrammes à colonnes de hauteur fixe (height-balanced histogram) . Le nombre de colonnes est choisi, et le même nombre de valeurs sont placées dans chaque colonne, ce qui donnera pour chaque colonne une "largeur" différente, un écart entre valeurs basse et haute, qui variera d'une colonne à l'autre. Est-ce que l'index sera utilisé? Si les histogrammes ont été générés, l'optimiseur disposera de cette analyse fine de la distribution des données. On peut avoir une idée de l'histogramme généré, comme dans l'exemple ci-dessous où on voit un exemple de requête permettant d'obtenir le contenu d'un histogramme. SELECT endpoint_number, endpoint_value FROM USER_HISTOGRAMS WHERE table_name = 'INVENTORIES' and column_name = 'QUANTITY_ON_HAND' ORDER BY endpoint_number; ENDPOINT_NUMBER --------------0 1 2 3 4 5 6 7 8 9 10 ENDPOINT_VALUE -------------0 27 42 57 74 98 123 149 175 202 353 Sur ce listing, une ligne correspond à une colonne de l'histogramme. Dans un tel histogramme, les valeurs de la colonne sont divisées en bandes telles qu'elles contiennent approximativement le même nombre de lignes. L'information utile que fournit l'histogramme est d'indiquer où se trouve le point terminant l'intervalle de valeurs. Autre représentation : Height-Balanced Histogram avec une distribution uniforme Height-Balanced Histogram avec une distribution non uniforme Les histogrammes s'avèreront donc utiles dès que l'on a affaire à des colonnes indexées à distribution irrégulière. La mise en oeuvre des histogrammes se fera par la commande analyze table ventes compute statistics for table for all indexed columns size 250; Dans ce cas on génèrera 250 colonnes pour notre histogramme. Les paramètres importants à prendre en compte : - nombre de colonnes (buckets) - fréquence des analyze - paramètre compute ou estimate de la commande analyze. Informations complémentaires : documentation Oracle Tuning 14/15 Récapitulatif de comparaison de l'accès aux données Sequentiel Simple Très coûteux Indexé Efficace – bonne gestion des intervalles Peu évolutif Arbre-B Hachage Efficace – bonne gestion Le plus efficace des intervalles Pas de gestion des Pb : traversée intervalles Tables statiques 15/15