Règles a suivre pour optimiser les requêtes SQL
Transcription
Règles a suivre pour optimiser les requêtes SQL
RÈGLES A SUIVRE POUR OPTIMISER LES REQUÊTES SQL 1. 2. 3. 4. 5. 6. 7. 8. 9. Le but de ce rapport est d’énumérer quelques règles pratiques à appliquer dans l’élaboration des requêtes. Il permettra de comprendre pourquoi certaines requêtes SQL sont longues à s’exécuter et d’éviter quelques pièges. Créer des index, ils améliorent la performance des requêtes qui retournent un faible pourcentage de rangées d’une table. Ne créer les index sur une table qu’après avoir inséré les données dans celle-ci Limiter le nombre d’index par table. Si des rangées sont insérées ou détruites dans une table, alors tous les index de cette table doivent être mis à jour. Si une table a beaucoup d’index et qu’on a beaucoup d’insertions, de mises à jour, de destructions, alors cela peut affecter la performance de l’application. Ne pas créer d’index pour les colonnes qui sont fréquemment modifiées car les pointeurs de l’index doivent constamment être mis à jour. Les clés primaires des tables doivent faire partie d’un index unique. Les colonnes qui servent de prédicats pour les requêtes avec jointure doivent faire partie d’un index. Les colonnes qui sont fréquemment utilisées dans les clauses WHERE de requêtes doivent faire partie d’un index. Un index peut être utilisé si une colonne de l’index est référencée dans la clause WHERE d’une requête et que la colonne indexée n’a pas été modifiée par une fonction et qu’on ne teste pas avec ‘IS NULL’ ou ‘IS NOT NULL’. Certains prédicats dans une clause ‘WHERE’ n’utiliseront pas d’index même si les colonnes sont incluses dans un index : • colonne1 > colonne2 • colonne1 < colonne2 • colonne1 >= colonne2 • colonne1 <= colonne2 • où colonne1 et colonne2 sont dans la même table. • colonne IS NULL • colonne NOT IN (...) • colonne != expression • colonne LIKE ‘%YYY’ Voici des exemples pour illustrer ces notions de base : On a une table qui représente les écritures comptables pour une compagnie fictive. CREATE TABLE ECRITURE ( ECR_NO NUMBER NOT NULL, ECR_COMPTE NUMBER NOT NULL, ECR_DATE DATE NOT NULL, ECR_TYPE CHAR(5), ECR_DESCRIPTION CHAR(80), ECR_MONTANT NUMBER(10,2) NOT NULL, ECR_SENS CHAR(1) NOT NULL) ; 1 CREATE UNIQUE INDEX ECR_IDX1 ON ECRITURE (ECR_NO) ; CREATE UNIQUE INDEX ECR_IDX2 ON ECRITURE (ECR_DATE,ECR_TYPE,ECR_SENS) ; Les requêtes suivantes utiliseront un index : SELECT * FROM ecriture WHERE ecr_no = 1234 ; Selon explain plan : Table access by rowid ecriture index unique scan ecr_idx1 SELECT * FROM ecriture WHERE ecr_no BETWEEN 200 AND 500 ; Selon explain plan : Table access by rowid ecriture Index unique scan ecr_idx1 Les requêtes suivantes n’utiliseront pas d’index car on modifie la colonne indexée : SELECT * FROM ecriture WHERE ecr_no*10=4320 ; Selon explain plan : Table access full ecriture SELECT * FROM ecriture WHERE to_char(ecr_date,’YYYYMMDD’) = ‘19970408’ ; Selon explain plan : Table access full ecriture Supposons qu’on a un index sur la colonne ecr_date. Les requêtes suivantes n’utiliseront pas l’index car les valeurs null n’ont pas d’index même si la colonne ecr_date fait partie d’un index. SELECT * FROM ecriture WHERE ecr_date IS NULL ; Selon explain plan : Table access full ecriture ou 2 SELECT * FROM ecriture WHERE ecr_date IS NOT NULL ; Selon explain plan : Table access full ecriture Par contre l’index sera utilisé dans la requête suivante : SELECT * FROM ecriture WHERE ecr_date = to_date(‘19970408’,’yyyymmdd’) ; Selon explain plan : Table access by rowid ecriture index range scan ecr_idx2 Les prédicat avec négations vont empêcher d’utiliser l’index comme par exemple : SELECT * FROM ecriture WHERE ecr_date != to_date(‘19970408’,’yyyymmdd’) ; Selon explain plan : Table access full ecriture Convertissez vos négations de la façon suivante : AVANT NOT > NOT >= NOT < NOT <= APRES <= < >= > 10. Oracle utilise un système de pointage pour les prédicats. Ils sont ordonnées du plus efficace au moins efficace. On peut en déduire qu’une requête comme : UPDATE ecriture WHERE rowid=ecr_rec.rowid ; sera plus rapide que la requête : UPDATE ecriture WHERE ecr_no=ecr_rec.ecr_no ; 1) rowid=constante 2) colonne indexée unique=constante 3) index entier concaténé unique=constante 3 4) index entier concaténé non unique=constante 5) index non unique=constante 6) index entier concaténé >= borne inférieure 7) index partiel concaténé 8) colonne indexée unique avec BETWEEN borne inférieure et borne supérieure 9) colonne indexée non unique avec BETWEEN borne inférieure et borne supérieure 10) colonne indexée unique <ou> constante 11) colonne indexée non unique 12) jointure seulement 13) max ou min d’une colonne indexée 14) order by index entier 15) recherche entière dans la table On remarque qu’un index unique est plus efficient qu’un index non unique car la clé a une seule entrée. 11. Un index concaténé contient 2 ou plusieurs colonnes. Il faut faire attention lors de l’usage de ces index. Lorsque l’on crée un index concaténé, la colonne la plus utilisée doit être spécifiée en premier. Détruire l’index ecr_idx2 et créer l’index suivant : CREATE INDEX ecr_idx3 ON ecriture (ecr_compte,ecr_date) ; La requête suivante n’utilisera pas l’index car elle ne réfère pas a la partie de tête de l’index : SELECT * FROM ecriture WHERE ecr_date=to_date9`19970408’,’yyyymmdd’) ; Selon explain plan : Table access full ecriture La requête suivante utilisera l’index car elle réfère a la partie de tête de l’index : SELECT * FROM ecriture WHERE ecr_compte=4000 ; Selon explain plan : Table access by rowid ecriture index range scan ecr_idx3 12. L’index ne sera pas utilisé dans une clause ‘OR’ si : • la requête contient une clause ‘connect by’ • la requête contient une jointure externe (outer join) • l’optimiseur décide de ne pas utiliser d’index car cela n’améliorait pas la performance de la requête. 4 13. Si le but d’une sous-requête est de vérifier l’existence d’une rangée alors il est préférable d’utiliser la clause ‘WHERE EXISTS’. Vérifier avec explain plan. CREATE TABLE compte ( cpt_no NUMBER NOT NULL, cpt_desc CHAR(50)) ; CREATE INDEX cpt_idx1 ON compte (cpt_no) ; SELECT * FROM ecriture e WHERE e.ecr_compte IN (SELECT c.cpt_no FROM compte c WHERE c.cpt_desc LIKE ‘compte%’) ; Selon explain plan : Nested loops View Sort Join Table access full compte Table access by rowid ecriture Index range scan ecr_idx3 SELECT * FROM ecriture e WHERE EXIST (SELECT 1 FROM compte c WHERE c.cpt_no=e.ecr_compte ‘compte%’) ; AND c.cpt_desc LIKE Selon explain plan : Filter Table access full ecriture Table access by rowid compte index range scan cpt_idx1 Si le nombre de rangées dans la table des écritures est plus élevé que dans la table des comptes alors un ‘Table access full’ pour écriture sera désavantageux. Donc la prémière requête semblerait la plus efficace puisqu’elle utilise l’index ecr_idx3 pour ecriture. 14. Vous devriez indexer les colonnes qui servent de jointure (Join) entre les tables. La Table qui est la dernière dans la clause FROM devient la table dominante (driving table) si les prédicats sont égaux selon le système de pointage. Si aucun index n’a été créé, alors Oracle devra effectuer un sort Merge Join qui créera une table temporaire triée pour chaque table dans la jointure. Ces tables temporaires seront fusionnées selon les prédicats de la jointure. Par exemple, la requête suivante sera longue a s’exécuter puisqu’elle exige une lecture complète des tables. SELECT e.ecr_type, c.cpt_nom FROM ecriture e, compte c WHERE e.ecr_compte=c.cpt_no AND e.ecr_type !=’J’; 5 En premier la table compte est mise dans une table temporaire et triée. Ceci exige une lecture complète de la table compte. SELECT cpt_no FROM compte ORDER BY cpt_no Après, la table ecriture est examinée et pour chaque rangée, l’information est mise dans la table temporaire et triée. Ceci exige une lecture complète de la table ecriture. SELECT ect_type, ecr_compte FROM ecriture WHERE ecr_type !=’J’ ORDER BY ecr_compte Et les deux tables sont fusionnées selon le prédicat de la jointure ecr_compte=cpt_no. Pour chaque rangée de la table compte (driving table), on doit aller chercher la rangée correspondante dans la table ecriture. Comme la table ecriture n’est pas indexée, on devra faire une lecture complète de la table pour trouver le numéro de compte correspondant. Selon explain plan : Merge Join Sort join Table access full compte Sort join Table access full ecriture. 15. La table qui n’est pas indexée dans un ‘join’ devient la table dominante. (driving table) DROP INDEX cpt_idx1 ; selon la requête suivante : SELECT e.ecr_type, c.cpt_nom FROM ecriture e, compte c WHERE e.ecr_compte=c.cpt_no AND e.ecr_type !=’J’ ; Selon explain plan : Nested loops Table access full compte Table access by rowid ecriture Index range scan ecr_idx3 S’il y a un index sur la colonne ecr_compte, alors compte devient la ‘driving table’. Donc pour chaque rangée de compte, Oracle ira chercher l’information correspondante (ecr_compte=cpt_no) dans la table ecriture avec l’index sur ecr_compte au lieu de faire peut-être une lecture complète de la table ecriture. 16. Placer la table avec le plus petit nombre de rangées qualifiées la dernière dans la clause ‘FROM’. 6