Technique 2 : Utiliser EXPLAIN/EXPLAIN ANALYZE pour lire le plan d'exécution
Technique 3 : Créer les bons index (simple, composé, couvrant)
Technique 4 : Éviter les fonctions sur les colonnes indexées (YEAR, LOWER, CAST)
Technique 5 : Optimiser les JOINs — indexer les colonnes de jointure, réduire avant de joindre
Technique 6 : Préférer EXISTS à IN pour les grosses sous-requêtes
Technique 7 : Keyset pagination plutôt que OFFSET élevé
Technique 8 : Batch processing pour les UPDATE/DELETE massifs
Technique 9 : Partitionnement de table pour les très grandes tables
Technique 10 : Vues matérialisées pour les calculs fréquents
L'optimisation SQL est un sujet qui revient de plus en plus en entretien technique, même pour des postes de data analyst. Les recruteurs veulent vérifier que vous ne vous contentez pas d'écrire des requêtes qui « marchent » — mais que vous comprenez pourquoi une requête est lente et comment la rendre rapide.
Thomas Leroy vous présente 10 techniques concrètes, de la plus basique à la plus avancée, avec des exemples mesurables et des explications claires.
Technique 1 : Arrêtez d'utiliser SELECT *
C'est la règle la plus simple et la plus souvent ignorée.
-- Mauvais : charge toutes les colonnesSELECT*FROM commandes WHERE statut ='livré';
-- Bon : charge uniquement ce dont vous avez besoinSELECT id, client_id, montant, date_livraison
FROM commandes
WHERE statut ='livré';
Pourquoi c'est important :
- **Moins de données transférées** entre le disque, la mémoire et le réseau
- **Les index couvrants** deviennent possibles (le moteur peut répondre sans accéder à la table)
- **Clarté** : vous et le recruteur savez exactement quelles colonnes sont utilisées
Sur une table avec 50 colonnes et des millions de lignes, la différence peut être d'un facteur 10x.
### Exemple concret avec mesure de performance
Prenez l'exemple de Sophie, développeuse chez une plateforme e-commerce. Elle optimise une requête sur la table `produits` qui contient 2 millions d'articles avec 45 colonnes (descriptions, métadonnées, images, etc.) :
```sql-- Avant (SELECT *) : 15 secondesSELECT*FROM produits WHERE categorie ='électronique';
-- Après (colonnes spécifiques) : 1.2 secondesSELECT id, nom, prix, stock
FROM produits
WHERE categorie ='électronique';
L'amélioration de 12.5x provient de la réduction de 45 colonnes à 4, permettant à l'index couvrant de répondre sans accéder au stockage principal.
<div class="callout callout-tip"><p class="callout-title">💡 Bon à savoir</p><p>Mentionnez systématiquement EXPLAIN en entretien lorsqu'on vous demande comment optimiser une requête. Même si vous ne pouvez pas l'exécuter, indiquer que votre première étape serait de lire le plan d'exécution montre une approche rigoureuse de diagnostic.</p>
</div>
## Technique 2 : Comprendre et utiliser EXPLAIN
`EXPLAIN` (ou `EXPLAIN ANALYZE` en PostgreSQL) affiche le plan d'exécution de la requête — la façon dont le moteur SQL va chercher les données.
```sql
EXPLAIN ANALYZE
SELECT*FROM commandes WHERE client_id =42;
Ce qu'il faut lire dans le plan d'exécution :
Opération
Signification
Performance
Seq Scan
Parcours séquentiel de toute la table
Lent sur grosses tables
Index Scan
Utilisation d'un index
Rapide
Index Only Scan
Réponse uniquement via l'index
Très rapide
Nested Loop
Boucle imbriquée pour les JOINs
OK pour petites tables
Hash Join
Table de hachage pour le JOIN
Bon pour grosses tables
Sort
Tri en mémoire ou sur disque
Coûteux si sur disque
En entretien, le fait de mentionner EXPLAIN et de savoir lire un plan d'exécution vous distingue immédiatement.
Cas pratique : diagnostic d'une requête lente
Marco, analyste data chez un courtier en assurance, fait face à une requête qui prenait 45 secondes :
-- Requête problématiqueSELECTCOUNT(*) FROM polices p
JOIN clients c ON p.client_id = c.id
WHERE c.age BETWEEN25AND35;
-- EXPLAIN révèle un Seq Scan sur 5M de lignes-- Après ajout d'index composé : 2.1 secondesCREATE INDEX idx_clients_age ON clients(age);
Le plan d'exécution EXPLAIN a révélé que la jointure se faisait après un Seq Scan complet sur clients, au lieu d'utiliser un index pour filtrer d'abord par âge.
💡 Bon à savoir
Pour progresser dans votre maîtrise des optimisations, consultez nos guides complémentaires sur les fonctions fenêtre SQL et les GROUP BY et HAVING, qui interviennent aussi dans les plans d'exécution complexes.
Technique 3 : Créer les bons index
Un index est comme l'index d'un livre : il permet de trouver une information sans parcourir toutes les pages.
Index simple
-- Créer un index sur la colonne la plus filtréeCREATE INDEX idx_commandes_client_id ON commandes(client_id);
Après cet index, WHERE client_id = 42 passe de Seq Scan à Index Scan.
Index composé
L'ordre des colonnes dans un index composé est crucial :
-- Index composé (client_id d'abord, puis date)CREATE INDEX idx_commandes_client_date
ON commandes(client_id, date_commande);
Cet index fonctionne pour :
WHERE client_id = 42 (utilise la première colonne)
WHERE client_id = 42 AND date_commande > '2026-01-01' (utilise les deux colonnes)
Mais PAS pour WHERE date_commande > '2026-01-01' seul (la première colonne manque)
Règle du « leftmost prefix » : un index composé est utilisable si la requête filtre sur un préfixe gauche des colonnes de l'index.
Index couvrant
Un index qui contient toutes les colonnes nécessaires à la requête :
-- Index couvrantCREATE INDEX idx_commandes_couv
ON commandes(client_id, date_commande, montant);
-- Cette requête n'accède même pas à la table (Index Only Scan)SELECT date_commande, montant
FROM commandes
WHERE client_id =42;
Spécificités par SGBD
PostgreSQL : Les index partiels permettent d'indexer seulement un sous-ensemble :
CREATE INDEX idx_commandes_actives
ON commandes(client_id) WHERE statut ='active';
MySQL : Les préfixes d'index économisent l'espace sur les colonnes texte longues :
CREATE INDEX idx_description ON produits(description(50));
SQL Server : Les index avec colonnes incluses évitent les key lookups :
CREATE INDEX idx_commandes_include
ON commandes(client_id) INCLUDE (montant, date_commande);
Quand ne PAS créer d'index
Tables très petites (< 1000 lignes) : le Seq Scan est plus rapide
Colonnes avec très peu de valeurs distinctives (booléens, statuts à 3 valeurs)
Tables avec beaucoup d'INSERT/UPDATE : chaque index ralentit les écritures
Erreurs courantes sur l'indexation
Erreur #1 : Créer trop d'index similaires
-- RedondantCREATE INDEX idx_orders_customer_id ON orders(customer_id);
CREATE INDEX idx_orders_customer_date ON orders(customer_id, order_date);
-- Le premier index est inutile
Erreur #2 : Oublier l'ordre dans les index composés
-- Mauvais ordre pour des requêtes sur date_commande seuleCREATE INDEX idx_orders_bad ON orders(customer_id, order_date);
-- Meilleur ordre selon le pattern de requêtesCREATE INDEX idx_orders_good ON orders(order_date, customer_id);
Erreur #3 : Index sur colonnes avec faible cardinalité
-- Inefficace : seulement 2 valeurs possiblesCREATE INDEX idx_users_gender ON users(gender);
⚠️ Attention
Appliquer une fonction sur une colonne indexée (YEAR, LOWER, CAST) désactive l'index. Le moteur doit alors faire un Seq Scan complet. Reformulez toujours les conditions pour opérer sur la valeur brute, pas sur une transformation de la colonne.
Technique 4 : Éviter les fonctions sur les colonnes indexées
Appliquer une fonction sur une colonne empêche l'utilisation de l'index :
-- Mauvais : l'index sur date_commande n'est PAS utiliséSELECT*FROM commandes
WHEREYEAR(date_commande) =2026;
-- Bon : reformuler comme une plageSELECT*FROM commandes
WHERE date_commande >='2026-01-01'AND date_commande <'2027-01-01';
-- Mauvais : l'index sur email n'est PAS utiliséSELECT*FROM utilisateurs
WHERELOWER(email) ='alice@example.com';
-- Bon : utiliser un index fonctionnel (PostgreSQL)CREATE INDEX idx_users_email_lower ON utilisateurs(LOWER(email));
Même principe avec les casts implicites — si la colonne est VARCHAR et que vous comparez avec un INT, le moteur peut caster la colonne et ne pas utiliser l'index.
Exemples de reformulation courantes
Mauvaise pratique
Bonne pratique
Gain
YEAR(date) = 2026
date >= '2026-01-01' AND date < '2027-01-01'
Index utilisé
SUBSTRING(code, 1, 3) = 'ABC'
code LIKE 'ABC%'
Index utilisé
UPPER(nom) = 'MARTIN'
Index fonctionnel ou collation insensible
Index utilisé
prix * tva > 100
prix > 100 / tva
Index utilisé
💡 Bon à savoir
Pour un index composé, respectez la règle du "leftmost prefix" : l'index (client_id, date_commande) est utilisé pour WHERE client_id = 42, mais pas pour WHERE date_commande > '2026-01-01' seul. Placez toujours la colonne la plus filtrée en premier dans l'index composé.
Technique 5 : Optimiser les JOINs
Indexer les colonnes de jointure
-- Assurez-vous que les deux côtés du JOIN sont indexésCREATE INDEX idx_commandes_client_id ON commandes(client_id);
-- clients.id est probablement déjà indexé (clé primaire)
Réduire les données avant le JOIN
-- Moins performant : joindre puis filtrerSELECT c.nom, SUM(co.montant)
FROM clients c
INNERJOIN commandes co ON c.id = co.client_id
WHERE co.date_commande >='2026-01-01'GROUPBY c.nom;
-- Plus performant : filtrer puis joindreWITH commandes_recentes AS (
SELECT client_id, montant
FROM commandes
WHERE date_commande >='2026-01-01'
)
SELECT c.nom, SUM(cr.montant)
FROM clients c
INNERJOIN commandes_recentes cr ON c.id = cr.client_id
GROUPBY c.nom;
En théorie, l'optimiseur devrait produire le même plan dans les deux cas. En pratique, sur des requêtes complexes avec plusieurs JOINs, l'aider avec des CTE (Common Table Expressions) peut faire une vraie différence.
Choisir le bon type de JOIN
INNER JOIN est généralement plus performant que LEFT JOIN (moins de lignes à traiter)
Utilisez LEFT JOIN uniquement quand vous en avez besoin
Exemple d'optimisation progressive sur une requête à 4 tables :
-- Requête initiale : 12 secondesSELECT u.nom, COUNT(c.id) as nb_commandes, SUM(p.prix) as total
FROM utilisateurs u
LEFTJOIN commandes c ON u.id = c.user_id
LEFTJOIN ligne_commande lc ON c.id = lc.commande_id
LEFTJOIN produits p ON lc.produit_id = p.id
WHERE u.date_inscription >='2025-01-01'GROUPBY u.id, u.nom;
-- Optimisé avec pré-agrégation : 2.3 secondesWITH stats_commandes AS (
SELECT c.user_id,
COUNT(c.id) as nb_commandes,
SUM(p.prix) as total
FROM commandes c
JOIN ligne_commande lc ON c.id = lc.commande_id
JOIN produits p ON lc.produit_id = p.id
GROUPBY c.user_id
)
SELECT u.nom,
COALESCE(sc.nb_commandes, 0) as nb_commandes,
COALESCE(sc.total, 0) as total
FROM utilisateurs u
LEFTJOIN stats_commandes sc ON u.id = sc.user_id
WHERE u.date_inscription >='2025-01-01';
Technique 6 : Utiliser EXISTS au lieu de IN pour les grosses sous-requêtes
-- Potentiellement lent : IN matérialise toute la sous-requêteSELECT nom FROM clients
WHERE id IN (SELECT client_id FROM commandes WHERE montant >1000);
-- Souvent plus rapide : EXISTS s'arrête au premier matchSELECT c.nom FROM clients c
WHEREEXISTS (
SELECT1FROM commandes co
WHERE co.client_id = c.id AND co.montant >1000
);
EXISTS peut s'arrêter dès qu'il trouve une correspondance, tandis que IN doit collecter toutes les valeurs d'abord.
Comparaison détaillée IN vs EXISTS vs JOIN
Méthode
Performance
Cas d'usage optimal
Attention
IN
Rapide si petite liste
Sous-requête retournant < 1000 valeurs
Lent si gros volume
EXISTS
Excellent
Test d'existence, gros volumes
Syntaxe plus complexe
INNER JOIN
Très rapide
Quand vous avez besoin des colonnes des 2 tables
Peut dupliquer les lignes
LEFT JOIN + IS NOT NULL
Rapide
Alternative à EXISTS
Moins lisible
Erreurs courantes avec les sous-requêtes
Erreur #1 : Utiliser IN avec des valeurs NULL
-- Peut ne pas retourner les résultats attendus si client_id contient NULLSELECT*FROM clients WHERE id IN (SELECT client_id FROM commandes);
-- Plus sûrSELECT c.*FROM clients c
WHEREEXISTS (SELECT1FROM commandes co WHERE co.client_id = c.id);
Erreur #2 : Sous-requêtes corrélées non optimisées
-- Lent : exécute la sous-requête pour chaque ligneSELECT*FROM clients c
WHERE (SELECTCOUNT(*) FROM commandes co WHERE co.client_id = c.id) >5;
-- Plus rapide : window functionSELECTDISTINCT c.*FROM clients c
JOIN (
SELECT client_id,
COUNT(*) OVER (PARTITIONBY client_id) as nb_commandes
FROM commandes
) co ON c.id = co.client_id
WHERE co.nb_commandes >5;
Pour approfondir, consultez notre article sur les sous-requêtes SQL.
Technique 7 : LIMIT et pagination efficace
Le problème du OFFSET
-- Lent pour les grandes valeurs de OFFSETSELECT*FROM articles
ORDERBY date_creation DESC
LIMIT 20OFFSET100000;
Le moteur doit lire 100020 lignes, les trier, puis en jeter 100000. Plus le OFFSET est grand, plus c'est lent.
La solution : keyset pagination
-- Rapide : utilise l'index directementSELECT*FROM articles
WHERE date_creation <'2026-03-15 10:30:00'ORDERBY date_creation DESC
LIMIT 20;
Au lieu de dire « saute 100000 lignes », on dit « donne-moi les lignes après ce curseur ». L'index est utilisé directement.
Implémentation pratique de la keyset pagination
-- Page 1 : récupérer les 20 premiers articlesSELECT id, titre, date_creation, auteur_id
FROM articles
ORDERBY date_creation DESC, id DESC
LIMIT 20;
-- Page suivante : utiliser les valeurs de la dernière ligneSELECT id, titre, date_creation, auteur_id
FROM articles
WHERE (date_creation, id) < ('2026-03-15 10:30:00', 12547)
ORDERBY date_creation DESC, id DESC
LIMIT 20;
Comparaison des performances
Exemple sur une table de 10 millions d'articles :
OFFSET 100000 : 8.5 secondes
Keyset pagination : 0.12 secondes (70x plus rapide)
L'amélioration devient exponentielle avec la taille de l'OFFSET.
Gestion de la pagination bidirectionnelle
Pour permettre la navigation dans les deux sens :
-- Page suivante (plus récents)SELECT id, titre, date_creation
FROM articles
WHERE date_creation >'2026-03-15 10:30:00'ORDERBY date_creation ASC, id ASC
LIMIT 20;
-- Page précédente (plus anciens) SELECT id, titre, date_creation
FROM articles
WHERE date_creation <'2026-03-15 09:15:00'ORDERBY date_creation DESC, id DESC
LIMIT 20;
Technique 8 : Batch processing pour les UPDATE/DELETE massifs
-- Mauvais : verrouille toute la tableDELETEFROM logs WHERE date_log <'2025-01-01';
-- Bon : traiter par lotsDELETEFROM logs
WHERE id IN (
SELECT id FROM logs
WHERE date_log <'2025-01-01'
LIMIT 10000
);
-- Répéter jusqu'à ce que 0 lignes soient supprimées
Avantages du traitement par lots :
Pas de verrouillage prolongé
Les autres requêtes continuent de fonctionner
Les logs de transaction restent gérables
Script complet pour le batch processing
-- PostgreSQL : suppression par lots avec boucle
DO $$
DECLARE
rows_deleted INTEGER :=1;
BEGIN
WHILE rows_deleted >0 LOOP
DELETEFROM logs
WHERE id IN (
SELECT id FROM logs
WHERE date_log <'2025-01-01'
LIMIT 10000
);
GET DIAGNOSTICS rows_deleted = ROW_COUNT;
RAISE NOTICE 'Supprimées: % lignes', rows_deleted;
-- Pause pour laisser respirer la base
PERFORM pg_sleep(1);
END LOOP;
END$$;
Stratégies d'UPDATE massifs
Pour les modifications importantes, la stratégie table temporaire peut être plus efficace :
-- Au lieu d'un UPDATE sur 10M lignes-- Créer une nouvelle table avec les bonnes donnéesCREATE TABLE commandes_new ASSELECT id, client_id, montant *1.1as montant, -- augmentation prix
date_commande, statut
FROM commandes;
-- Swapper atomiquementBEGIN;
DROPTABLE commandes;
ALTER TABLE commandes_new RENAME TO commandes;
-- Recréer index et contraintesCOMMIT;
Erreurs fréquentes dans le batch processing
Erreur #1 : Lots trop petits ou trop gros
-- Trop petit : overhead excessif
LIMIT 100; -- 1000 itérations pour 100K lignes-- Trop gros : verrous prolongés
LIMIT 1000000; -- Risque de timeout-- Optimal : entre 1K et 10K selon la complexité
LIMIT 5000;
Erreur #2 : Oublier les conditions d'arrêt
-- Risque de boucle infinie si la condition WHERE ne correspond à rien
WHILE rows_affected >0 LOOP
UPDATEtableSET col =valueWHERE impossible_condition;
END LOOP;
Erreur #3 : Ne pas monitorer la progression
-- Ajoutez toujours des logs pour suivre l'avancement
RAISE NOTICE 'Lot %, lignes affectées: %', iteration, rows_affected;
Technique 9 : Partitionnement de table
Le partitionnement divise physiquement une grande table en segments plus petits :
-- PostgreSQL : partitionnement par rangeCREATE TABLE commandes (
id SERIAL,
date_commande DATE,
montant DECIMAL(10,2)
) PARTITIONBYRANGE (date_commande);
CREATE TABLE commandes_2025 PARTITIONOF commandes
FORVALUESFROM ('2025-01-01') TO ('2026-01-01');
CREATE TABLE commandes_2026 PARTITIONOF commandes
FORVALUESFROM ('2026-01-01') TO ('2027-01-01');
Quand vous filtrez par date, le moteur ne parcourt que la partition concernée (partition pruning). Sur des tables de centaines de millions de lignes, le gain est considérable.
Types de partitionnement
Partitionnement par range (le plus courant) :
-- Par dateCREATE TABLE ventes_2026_q1 PARTITIONOF ventes
FORVALUESFROM ('2026-01-01') TO ('2026-04-01');
-- Par valeur numériqueCREATE TABLE commandes_petites PARTITIONOF commandes
FORVALUESFROM (0) TO (1000);
Partitionnement par hash (distribution équilibrée) :
Maintenance parallèle : VACUUM, REINDEX par partition
Archivage simple : DROP d'une partition = suppression instantanée
Requêtes parallèles : travail distribué sur plusieurs partitions
Cas d'usage optimal
Clara, DBA chez une fintech, gère une table transactions de 500M de lignes. Après partitionnement mensuel :
Requêtes sur le mois courant : de 45s à 1.2s
Archivage des données anciennes : de 6h à 30 secondes (DROP partition)
VACUUM mensuel au lieu d'hebdomadaire
Stratégies de partitionnement par secteur
E-commerce : Partitionnement par date de commande
-- Permet l'archivage automatique des anciennes commandesCREATE TABLE commandes_2026_01 PARTITIONOF commandes
FORVALUESFROM ('2026-01-01') TO ('2026-02-01');
SaaS/Analytics : Partitionnement par tenant (client)
CREATE TABLE events_client_1001 PARTITIONOF events
FORVALUESFROM (1001) TO (1002);
Technique 10 : Matérialiser les calculs fréquents
Vues matérialisées
-- Créer une vue matérialisée pour un rapport fréquentCREATE MATERIALIZED VIEW mv_stats_vendeurs ASSELECT
vendeur,
DATE_TRUNC('month', date_vente) AS mois,
COUNT(*) AS nb_ventes,
SUM(montant) AS total,
AVG(montant) AS moyenne
FROM ventes
GROUPBY vendeur, DATE_TRUNC('month', date_vente);
-- Requêter la vue matérialisée (instantané)SELECT*FROM mv_stats_vendeurs WHERE vendeur ='Alice';
-- Rafraîchir quand les données changent
REFRESH MATERIALIZED VIEW mv_stats_vendeurs;
Colonnes calculées / Generated columns
-- PostgreSQL 12+ALTER TABLE commandes
ADDCOLUMN mois_commande DATE GENERATED ALWAYS AS
(DATE_TRUNC('month', date_commande)) STORED;
CREATE INDEX idx_commandes_mois ON commandes(mois_commande);
Stratégies de rafraîchissement
Rafraîchissement complet (relit toutes les données) :
Rafraîchissement incrémental (pour les trop grosses vues) :
-- Recréer seulement le mois courantINSERT INTO mv_stats_vendeurs_incremental
SELECT vendeur, DATE_TRUNC('month', NOW()),
COUNT(*), SUM(montant), AVG(montant)
FROM ventes
WHERE DATE_TRUNC('month', date_vente) = DATE_TRUNC('month', NOW())
GROUPBY vendeur;
Cas pratique : agrégations commerciales
Yannick, analyste chez un retailer multicanal, doit afficher des tableaux de bord avec 50+ métriques à chaque actualisation. Les requêtes prenaient 8-12 secondes. Après matérialisation :
-- Vue matérialisée combinant 7 tablesCREATE MATERIALIZED VIEW vw_dashboard_kpis ASWITH daily_sales AS (
SELECT DATE_TRUNC('day', o.date_commande) asdate,
p.categorie,
COUNT(*) as nb_commandes,
SUM(ol.quantite) as nb_articles,
SUM(ol.prix_unitaire * ol.quantite) as total_ht
FROM orders o
JOIN order_lines ol ON o.id = ol.order_id
JOIN products p ON ol.product_id = p.id
WHERE o.status ='completed'GROUPBY1, 2
)
SELECT*FROM daily_sales;
-- Rafraîchissement nightly (05:00 UTC)-- INSERT INTO admin_logs VALUES (NOW(), 'Dashboard refreshed')
Résultat : requêtes de tableau de bord < 0.3 secondes au lieu de 10+ secondes.
Erreurs fréquentes avec les vues matérialisées
Erreur #1 : Oublier de rafraîchir
-- Données obsolètes sans notification-- Les utilisateurs voient des chiffres d'hier-- Solution : automatiser le rafraîchissement
Erreur #2 : Matérialiser des vues rarement consultées
-- Gaspille de l'espace disque-- Utilisez des vues normales (virtuelles) si consultées < 1x/jour
Erreur #3 : Accumulation excessive de vues matérialisées
-- 100+ vues matérialisées = maintenance complexe-- Limiter à 10-20 vues pour les principales métriques
Questions fréquentes
Quelle est la différence entre un index simple et un index composé ?
Un index simple (une colonne) est rapide pour filtrer sur cette seule colonne. Un index composé (plusieurs colonnes) est plus flexible : il peut servir pour des filtres utilisant le préfixe gauche des colonnes. Par exemple, l'index (client_id, date_commande) fonctionne pour WHERE client_id = 42 ET pour WHERE client_id = 42 AND date = '2026-01-01', mais pas pour WHERE date = '2026-01-01' seul. Toujours placer la colonne la plus filtrée en premier.
Dois-je toujours utiliser EXISTS au lieu de IN ?
Non. IN est plus lisible et généralement efficace pour les sous-requêtes retournant moins de 1000 valeurs. EXISTS est meilleur pour les gros volumes ou les sous-requêtes corrélées complexes. Sur les SGBD modernes (PostgreSQL 13+, MySQL 8.0+), l'optimiseur transforme souvent IN en EXISTS automatiquement. Préférez la lisibilité sauf sur des requêtes très lentes.
Comment choisir entre OFFSET/LIMIT et keyset pagination ?
Utilisez OFFSET/LIMIT pour les petites pages (< 10 pages consultées en général). Keyset pagination est obligatoire pour les grands volumes de données ou la pagination infinie. Keyset nécessite un tri stable et un curseur, mais offre une performance indépendante de la position dans la liste.
Combien de vues matérialisées devrais-je créer ?
Limitez-vous à 10-20 vues pour les métriques vraiment fréquentes. Chaque vue matérialisée consomme du disque et nécessite des rafraîchissements. Mesurez l'impact : si une requête prend 1s, créer une vue matérialisée rarement consultée est du gaspillage. Utilisez des vues normales pour le reste.
Pourquoi mon index n'est-il pas utilisé alors que j'ai créé la bonne colonne ?
Causes courantes : (1) Fonction appliquée sur la colonne (YEAR, LOWER, etc.) ; (2) Cast implicite de type ; (3) Condition avec OR impliquant plusieurs colonnes ; (4) Statistiques obsolètes (ANALYZE requis) ; (5) Selectivité trop faible (booléens, énumérations). Utilisez EXPLAIN ANALYZE pour diagnostiquer.
Le partitionnement ralentit-il les requêtes sans filtrage de partition ?
Oui, légèrement. Une requête sans filtre sur la clé de partitionnement doit unionner tous les partitions, ce qui ajoute un peu d'overhead. Le partitionnement vaut la peine seulement si (1) la table > 10GB, (2) vous filtrez souvent sur la clé de partitionnement, ou (3) vous avez besoin d'archivage rapide.
Comment optimiser une requête sans pouvoir ajouter d'index (droits limités) ?
Réécrivez la requête : (1) Séparez en requêtes plus simples ; (2) Utilisez des CTE pour pré-filtrer ; (3) Éliminez les sous-requêtes corrélées ; (4) Reformulez les conditions (YEAR → plages de dates) ; (5) Demandez à votre DBA les index nécessaires avec données de production.
Prêt à vous entraîner ?
50 exercices SQL interactifs avec éditeur en ligne, chronomètre et feedback IA.