jeudi 15 janvier 2026

Optimiser SQL



1️⃣ Pourquoi une requête SQL est lente ?

En général, c’est à cause de :

  • ❌ Absence ou mauvais index

  • ❌ Requêtes qui scannent toute la table (Full Table Scan)

  • ❌ Mauvaise écriture SQL

  • ❌ Trop de données retournées

  • ❌ Fonctions dans WHERE

  • ❌ Mauvais JOIN

  • ❌ Verrouillage (locks, deadlocks)


2️⃣ Règle n°1 : utiliser les index correctement

❌ Mauvais exemple (lent)

SELECT *
FROM Orders
WHERE YEAR(OrderDate) = 2024;

👉 Problème :

  • La fonction YEAR() empêche l’utilisation d’un index


✅ Bon exemple (rapide)

SELECT OrderId, CustomerId, OrderDate
FROM Orders
WHERE OrderDate >= '2024-01-01'
  AND OrderDate < '2025-01-01';

✔ Index utilisable
✔ Moins de données lues


🔹 Créer un index

CREATE INDEX IX_Orders_OrderDate
ON Orders(OrderDate);

3️⃣ Règle n°2 : éviter SELECT *

❌ Mauvais

SELECT *
FROM Customers;

✅ Bon

SELECT CustomerId, Name, Email
FROM Customers;

👉 Moins :

  • I/O disque

  • Mémoire

  • Réseau


4️⃣ Règle n°3 : Index sur les colonnes de JOIN

❌ Lent

SELECT *
FROM Orders o
JOIN Customers c ON o.CustomerId = c.CustomerId;

✅ Optimisé

CREATE INDEX IX_Orders_CustomerId ON Orders(CustomerId);
CREATE INDEX IX_Customers_CustomerId ON Customers(CustomerId);

👉 Le JOIN devient Index Seek au lieu de Scan


5️⃣ Règle n°4 : filtrer AVANT de JOIN

❌ Mauvais

SELECT *
FROM Orders o
JOIN Customers c ON o.CustomerId = c.CustomerId
WHERE c.Country = 'FR';

✅ Meilleur

SELECT o.OrderId, o.OrderDate
FROM Orders o
JOIN Customers c 
  ON o.CustomerId = c.CustomerId
WHERE c.Country = 'FR';

➕ Index :

CREATE INDEX IX_Customers_Country
ON Customers(Country);

6️⃣ Règle n°5 : éviter les sous-requêtes coûteuses

❌ Lent

SELECT *
FROM Orders
WHERE CustomerId IN (
    SELECT CustomerId FROM Customers WHERE Country = 'FR'
);

✅ Plus rapide

SELECT o.*
FROM Orders o
JOIN Customers c ON o.CustomerId = c.CustomerId
WHERE c.Country = 'FR';

7️⃣ Règle n°6 : utiliser EXISTS au lieu de IN (gros volumes)

❌ Lent

SELECT *
FROM Orders o
WHERE o.CustomerId IN (
  SELECT CustomerId FROM Customers
);

✅ Rapide

SELECT *
FROM Orders o
WHERE EXISTS (
  SELECT 1 FROM Customers c
  WHERE c.CustomerId = o.CustomerId
);

8️⃣ Règle n°7 : index couvrant (INCLUDE)

🎯 Objectif

Éviter les accès supplémentaires à la table

CREATE INDEX IX_Orders_Covering
ON Orders(CustomerId)
INCLUDE (OrderDate, TotalAmount);

✔ La requête est satisfaite uniquement par l’index


9️⃣ Règle n°8 : attention aux fonctions dans WHERE

❌ Lent

WHERE UPPER(Email) = 'TEST@MAIL.COM'

✅ Rapide

WHERE Email = 'test@mail.com'

Ou :

CREATE INDEX IX_Customers_Email
ON Customers(Email);

🔟 Règle n°9 : Pagination efficace

❌ Mauvais (OFFSET trop grand)

SELECT *
FROM Orders
ORDER BY OrderDate
OFFSET 100000 ROWS FETCH NEXT 20 ROWS ONLY;

✅ Bon (keyset pagination)

SELECT *
FROM Orders
WHERE OrderDate < @LastOrderDate
ORDER BY OrderDate DESC
FETCH NEXT 20 ROWS ONLY;

1️⃣1️⃣ Lire le plan d’exécution (INDISPENSABLE)

SQL Server

SET STATISTICS IO ON;
SET STATISTICS TIME ON;

Ou :

  • Actual Execution Plan

  • Rechercher :

    • ❌ Table Scan

    • ❌ Key Lookup

    • ❌ Nested Loop coûteux


1️⃣2️⃣ Exemple concret complet

Tables

Customers(CustomerId, Name, Country)
Orders(OrderId, CustomerId, OrderDate, Total)

Requête optimisée

SELECT o.OrderId, o.Total
FROM Orders o
JOIN Customers c ON o.CustomerId = c.CustomerId
WHERE c.Country = 'FR'
  AND o.OrderDate >= '2024-01-01';

Index

CREATE INDEX IX_Customers_Country_Id
ON Customers(Country, CustomerId);

CREATE INDEX IX_Orders_Customer_Date
ON Orders(CustomerId, OrderDate)
INCLUDE (Total);

1️⃣3️⃣ Checklist rapide (à retenir)

✅ Pas de SELECT *
✅ Index sur WHERE / JOIN / ORDER BY
✅ Pas de fonctions sur colonnes filtrées
✅ Préférer EXISTS
✅ Lire le plan d’exécution
✅ Limiter le volume de données



Plan d’exécution avec EXPLAIN PLAN, et c’est l’outil n°1 pour optimiser les performances SQL.

1️⃣ Qu’est-ce que EXPLAIN PLAN dans Oracle ?

EXPLAIN PLAN montre comment Oracle prévoit d’exécuter ta requête :

  • Index ou Full Table Scan

  • Ordre des JOIN

  • Coût estimé

  • Méthode de JOIN (Nested Loop, Hash Join…)

⚠️ Important :

  • EXPLAIN PLAN = plan estimé

  • Pour le plan réel, on utilise DBMS_XPLAN.DISPLAY_CURSOR


2️⃣ Pré-requis (table PLAN_TABLE)

Dans la plupart des bases Oracle modernes, elle existe déjà.

Si besoin :

@?/rdbms/admin/utlxplan.sql

3️⃣ Syntaxe de base : EXPLAIN PLAN

Exemple simple

EXPLAIN PLAN FOR
SELECT *
FROM employees
WHERE department_id = 10;

👉 Cela n’exécute pas la requête
👉 Ça stocke le plan dans PLAN_TABLE


4️⃣ Afficher le plan d’exécution

Méthode classique

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

Résultat typique

--------------------------------------------------------------------------------
| Id | Operation           | Name      | Rows | Cost |
--------------------------------------------------------------------------------
|  0 | SELECT STATEMENT    |           |   10 |   3  |
|  1 | TABLE ACCESS FULL   | EMPLOYEES |   10 |   3  |
--------------------------------------------------------------------------------

👉 Ici :

  • TABLE ACCESS FULL → pas d’index utilisé


5️⃣ Optimisation simple avec index

Créer un index

CREATE INDEX IDX_EMP_DEPT
ON employees(department_id);

Rejouer le plan

EXPLAIN PLAN FOR
SELECT *
FROM employees
WHERE department_id = 10;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

Nouveau plan

--------------------------------------------------------------------------------
| Id | Operation        | Name          | Cost |
--------------------------------------------------------------------------------
|  0 | SELECT STATEMENT |               |   1  |
|  1 | INDEX RANGE SCAN | IDX_EMP_DEPT  |   1  |
--------------------------------------------------------------------------------

Index Range Scan
✅ Coût réduit → meilleure performance


6️⃣ Exemple avec JOIN (cas réel)

Requête

EXPLAIN PLAN FOR
SELECT e.employee_id, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE d.location_id = 1700;

Plan affiché

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

Plan possible

| Id | Operation           | Name          |
------------------------------------------------
|  0 | SELECT STATEMENT    |               |
|  1 | NESTED LOOPS        |               |
|  2 | TABLE ACCESS FULL   | DEPARTMENTS   |
|  3 | TABLE ACCESS FULL   | EMPLOYEES     |

❌ Deux Full Table Scan


7️⃣ Optimisation du JOIN

Index recommandés

CREATE INDEX IDX_DEPT_LOC
ON departments(location_id);

CREATE INDEX IDX_EMP_DEPT
ON employees(department_id);

Nouveau plan

| Id | Operation           | Name          |
------------------------------------------------
|  0 | SELECT STATEMENT    |               |
|  1 | NESTED LOOPS        |               |
|  2 | INDEX RANGE SCAN    | IDX_DEPT_LOC  |
|  3 | INDEX RANGE SCAN    | IDX_EMP_DEPT  |

✅ Index utilisés
✅ JOIN rapide


8️⃣ Plan RÉEL (beaucoup mieux)

EXPLAIN PLAN ne montre pas le plan réellement exécuté.

Étape 1 : exécuter la requête

SELECT /*+ gather_plan_statistics */
e.employee_id, d.department_name
FROM employees e
JOIN departments d ON e.department_id = d.department_id
WHERE d.location_id = 1700;

Étape 2 : afficher le plan réel

SELECT *
FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'ALLSTATS LAST'));

👉 Tu verras :

  • Nombre de lignes réelles

  • Temps réel

  • Écarts estimé vs réel


9️⃣ Comment optimiser à partir du plan ?

À surveiller ⚠️

ÉlémentAction
TABLE ACCESS FULLAjouter index
COST élevéRéécrire requête
NESTED LOOP sur gros volumesForcer HASH JOIN
FILTERÉviter fonctions
KEY LOOKUPIndex couvrant

🔟 Forcer un plan (si nécessaire)

⚠️ À utiliser avec prudence

SELECT /*+ INDEX(e IDX_EMP_DEPT) */
*
FROM employees e
WHERE department_id = 10;

1️⃣1️⃣ Résumé rapide (entretien / pratique)

EXPLAIN PLAN FOR → plan estimé
DBMS_XPLAN.DISPLAY → affichage
DBMS_XPLAN.DISPLAY_CURSOR → plan réel
✅ Index = arme n°1
✅ Toujours comparer estimé vs réel



Aucun commentaire:

Enregistrer un commentaire

Optimiser SQL Lượt xem: