Dans la plupart des projets, une latence élevée des requêtes MySQL est due à une faible Conception de requêtes – pas par l'hébergement. Je montre concrètement comment optimisation de base de données qui réduit la latence grâce à des index, des stratégies de mise en mémoire tampon et de connexion, et pourquoi l'infrastructure en est rarement la cause principale.
Points centraux
Les points clés suivants m'aident à analyser avec précision les accès lents à la base de données.
- Indices décident des requêtes rapides ou lentes.
- Structure de requête Tout comme JOIN vs. sous-requête influence la durée d'exécution.
- mise en commun Réduit les frais généraux liés à l'établissement de la connexion.
- Pool de mémoire tampon réduit la latence des E/S et les blocages.
- Suivi Sépare clairement le temps de requête, le temps serveur et le temps réseau.
Pourquoi l'hébergement est rarement le goulot d'étranglement
J'entends souvent dire que Latence est dû à un „ hébergement lent “. C'est parfois vrai, mais les leviers les plus importants se trouvent dans Requêtes. Les mesures montrent des différences significatives entre les instances MySQL internes et externes : 0,0005 s en interne contre 0,02 à 0,06 s en externe par requête (source [1]). Même ce facteur 50x a moins d'importance dans la pratique si les requêtes sont correctement indexées, bien structurées et adaptées au cache. Si vous exécutez la même requête cent fois sans index, vous perdez du temps, quelle que soit la distance par rapport au serveur. Je vérifie donc d'abord le profil de la requête avant de soupçonner l'infrastructure.
Ce qui influence réellement la latence des requêtes MySQL
Le temps de requête comprend le temps d'envoi du client, le traitement du serveur et Réseau ensemble. Dans les applications Web typiques, la Traitement sur le serveur DB, notamment lors d'analyses complètes de tables ou de jointures erronées. Sans index adaptés, le nombre de pages lues augmente, l'optimiseur choisit des plans sous-optimaux et le CPU surchauffe. Parallèlement, une application bavarde peut inutilement alourdir le temps réseau en effectuant de nombreux petits allers-retours. Je mesure donc séparément : client->serveur, exécution et serveur->client, afin de voir clairement le véritable goulot d'étranglement (cf. [5]).
Transactions, verrous et isolation
Les facteurs de latence souvent négligés sont les suivants Locks et trop longues Transactions. InnoDB fonctionne avec MVCC et des verrous de ligne, mais sous LECTURE RÉPÉTABLE s'ajoutent les verrous de décalage, qui peuvent ralentir les mises à jour de plage. Les transactions longues conservent les anciennes versions dans l'annulation, augmentent la pression sur la mémoire et les E/S et bloquent les opérations d'écriture concurrentes. Je veille donc à ce que les transactions soient courtes : uniquement les instructions minimales nécessaires, des validations précoces, pas d'attente pour les interactions utilisateur au sein de la transaction.
Pour UPDATE/DELETE, je mise sur sargable Conditions WHERE avec des index adaptés afin d'éviter de verrouiller inutilement un grand nombre de lignes. Je détecte les verrous d'attente via le schéma de performance (events_waits, lock_instances) et le journal des blocages ; je résous les schémas récurrents à l'aide d'index améliorés, d'autres séquences d'accès ou, si cela est techniquement possible, à l'aide de SELECT … FOR UPDATE SKIP LOCKED, pour éviter que les Worker ne soient bloqués. Le innodb_lock_wait_timeout Je dimensionne délibérément de manière conservatrice afin que les erreurs soient visibles rapidement, au lieu de bloquer les requêtes pendant plusieurs minutes.
Indexation : le levier le plus puissant
Sans Indices MySQL recherche dans des tables complètes, même les petites tables génèrent alors des CPU-Charge. Je commence toujours par EXPLAIN, je regarde type=ALL, key=NULL et le rapport entre rows et rows_examined. Les index composites sur les colonnes WHERE et JOIN réduisent considérablement le nombre de lignes analysées. L'ordre dans l'index reste important : les colonnes sélectives d'abord, puis les autres filtres. Si vous souhaitez approfondir le sujet, lisez mes remarques sur Comprendre les index MySQL et vérifie des modèles de requête concrets (cf. [3]).
Structure de requête : JOIN au lieu de sous-requêtes
Les sous-requêtes imbriquées conduisent souvent à une dégradation des performances. plans comme équivalent JOINs. Je remplace les sous-sélections corrélées, qui recalculent chaque ligne, par des jointures claires avec des index appropriés. Pour ce faire, j'applique des filtres le plus tôt possible et je veille à utiliser des conditions sargables (par exemple, colonne = valeur au lieu de fonction(colonne)). LIMIT avec ORDER BY nécessite un index de support, sinon MySQL trie en mémoire ou sur le disque. J'accélère également COUNT(*) sur de grandes plages à l'aide d'index de couverture étroits, au lieu de lire la ligne entière.
Tables temporaires, tri et limites de mémoire
L'absence d'index de tri ou de regroupement oblige MySQL à Tri de fichiers et les tables temporaires. Les petits fichiers temporaires dans la RAM ne posent pas de problème ; s'ils dépassent tmp_table_size/max_heap_table_size ou contiennent BLOB/TEXT, passez à disque – la latence augmente considérablement. Je veille donc à ce que ORDER BY/GROUP BY soient couverts par des index appropriés et je réduis la largeur des colonnes ainsi que les listes SELECT afin que les structures temporaires restent petites.
Je dimensionne les tampons Join et Sort de manière ciblée, non pas de manière globale, mais en fonction de la charge de travail réelle. Des tampons trop volumineux sur de nombreuses sessions simultanées entraînent eux-mêmes un manque de mémoire. Je trouve des indications dans le schéma de performance (tmp_disk_tables, sort_merge_passes) et dans le slow log (using temporary; using filesort). Lorsque LIMIT avec ORDER BY est inévitable, j'utilise un index sur la colonne de tri et un filtre pour aider MySQL à trouver la plage indexé et peut s'interrompre prématurément.
Requêtes N+1 et pièges ORM
Le modèle classique N+1 multiplie les Latence: une liste se charge, et chaque entrée est suivie d'une seconde Consultation. Je le reconnais à un nombre élevé de requêtes par demande et je remplace les requêtes suivantes par des clauses JOIN ou IN. Les ORM ont tendance à générer des SQL génériques, mais pas optimaux ; j'interviens ici avec une configuration de chargement paresseux/avide. Lorsque cela est judicieux, je choisis spécifiquement des colonnes SELECT au lieu de SELECT *. Cela réduit la quantité de données transférées et les caches fonctionnent plus efficacement.
Types de données et conception des clés primaires
Une bonne conception de schéma permet de réduire la latence à la source. J'utilise la types de données les plus petits adaptés (TINYINT/SMALLINT au lieu de BIGINT, longueurs VARCHAR plus courtes), car chaque octet réduit la pression sur l'index et le pool de tampons. Les collations influencent les comparaisons et la sélectivité : les collations insensibles à la casse simplifient la recherche, mais peuvent être moins sélectives lors de recherches par modèle. Pour les longues colonnes de texte, j'utilise si nécessaire Index préfixés, si les premiers caractères sont suffisamment sélectifs.
Dans InnoDB, le clé primaire l'ordre physique et se trouve dans chaque index secondaire. Un index étroit, PK monotone (par exemple BIGINT AUTO_INCREMENT) minimise les fractionnements de pages, les besoins en RAM et l'amortissement des écritures. Les UUIDv4 aléatoires entraînent des divisions constantes et des pages froides ; si des UUID sont nécessaires, je choisis des variantes avec un ordre temporel (par exemple, des UUID triables) ou je sépare les PK techniques des clés métier. Les PK larges et composites rendent chaque index secondaire plus coûteux – une stratégie PK claire est particulièrement utile dans ce cas.
Mise en commun des connexions et cycle de vie des connexions
Chaque connexion coûte Temps et charge Ressources. Si je crée une nouvelle connexion pour chaque requête, la latence perçue augmente. J'utilise le pool de connexions afin que les workers réutilisent les sessions existantes. Je dimensionne les délais d'inactivité et les connexions maximales de manière à amortir proprement les pics. Des outils tels que ProxySQL ou des poolers spécifiques à un langage réduisent sensiblement les pics de latence, en particulier en cas de nombreuses requêtes parallèles.
Déclarations préparées, stabilité du plan et gestion des statistiques
L'analyse syntaxique et l'optimisation prennent beaucoup de temps lorsque le QPS est élevé. Déclarations préparées réduisent cette surcharge, stabilisent les plans et améliorent le traitement des requêtes dans la surveillance. Les caractères de remplacement empêchent également le mosaïquage des plans grâce à des littéraux en constante évolution. Si les estimations de l'optimiseur deviennent imprécises (les lignes vs. les lignes examinées dérivent fortement), je mets à jour les statistiques (ANALYSE TABLE) et, en cas de biais prononcé des données Histogrammes . L'optimiseur prend ainsi de meilleures décisions en matière d'ordre de jointure et d'indexation.
Avec EXPLAIN ANALYZE Je compare les estimations avec les en effet lignes traitées et voir où la cardinalité ou les filtres ont été mal évalués. Index invisibles Je l'utilise pour tester des alternatives sans risque, sans avoir à modifier en profondeur le système du produit. Si les plans deviennent incohérents en raison d'un décalage des paramètres, les astuces de requête peuvent aider ponctuellement, mais je ne les utilise que lorsque les statistiques et les index sont propres.
Gestion des tampons et caches
Le pool de tampons InnoDB conserve les données chaudes Données dans la RAM et réduit les coûts élevés disque-Accès. Je règle la taille à environ 70-80 % de la mémoire disponible de l'hôte de la base de données, j'observe le taux d'accès au pool de tampons et je vérifie les vidages de pages (cf. [3]). Un nombre trop élevé de pages sales et un tampon de journalisation insuffisant réduisent le débit. Des volumes de journalisation et de données séparés évitent les conflits d'E/S et stabilisent les performances d'écriture. Ce réglage fin fonctionne indépendamment du fournisseur : il s'agit uniquement d'une question de configuration.
Caches externes au lieu de cache de requêtes
Le cache de requêtes MySQL était un frein en cas de parallélisme élevé et a été supprimé dans la version 8.0. J'utilise Redis ou Memcached pour les charges de lecture récurrentes et je mets en cache des objets bien définis. Je sépare strictement les clés de cache par client et par langue afin d'éviter toute confusion. Je contrôle l'invalidation en fonction des événements, par exemple après une mise à jour via un événement. Cela me permet de soulager la base de données, de réduire les allers-retours et de stabiliser considérablement les temps de réponse.
Réplication et mise à l'échelle de la lecture
Pour les charges de lecture évolutives, j'utilise Répliques en lecture. Je n'y achemine que les lectures tolérantes et conserve le Décalage de réplication afin que les utilisateurs ne voient pas de données obsolètes. Je résous le problème „ Read-your-writes “ avec des sessions persistantes ou un routage ciblé vers le primaire immédiatement après une opération d'écriture. Les transactions longues, les lots volumineux ou les DDL augmentent le décalage – dans ce cas, je prévois des fenêtres hors pointe et des blocs de validation plus petits.
Important : la réplication ne masque pas les requêtes incorrectes, elle multiplié Elle. Je commence par nettoyer les index et la structure des requêtes. Ce n'est qu'ensuite que le fractionnement des lectures prend tout son sens. Du côté de la surveillance, je corrèle les pics de latence avec les pics d'écriture et je vérifie si les paramètres binlog et flush correspondent aux exigences en matière de latence et de durabilité.
Surveillance avec contexte
Sans contexte, toute Métriques incomplet, c'est pourquoi je sépare Temps Propre : client, réseau, serveur. J'observe les lignes examinées par rapport aux lignes envoyées, la répartition de la durée des requêtes (P95/P99) et les temps d'attente pour les verrous. Je corrèle les journaux de requêtes lentes avec les pics de charge de travail afin d'identifier les causes. Je mesure le retard de réplication séparément, car les opérations d'écriture lentes retardent les répliques de lecture (cf. [5]). C'est la seule façon de décider si je dois modifier la conception des requêtes, les index ou l'infrastructure.
WordPress : chargement automatique et tableau d'options
De nombreux sites WordPress ralentissent via le Optionstableau et trop grand chargement automatique-Données. Je vérifie donc régulièrement la taille des options autoloaded et déplace les entrées rarement utilisées vers on-demand. Les index sur option_name et les SELECTS allégés empêchent les analyses complètes. Si je gère les événements Cron et supprime les transitoires, la base de données reste allégée. Si vous avez besoin d'aide pour démarrer, consultez mes remarques sur Options d'autoload pour des étapes pratiques de réglage.
Partitionnement et archivage
Partitionnement m'aide surtout avec les très grands tableaux qui s'allongent avec le temps (journaux, événements). Elle accélère moins la requête individuelle, mais permet Taille et maintenance facile : les anciennes partitions peuvent être rapidement supprimées, les réorganisations peuvent être planifiées. Je choisis quelques partitions de plage pertinentes (par exemple, mensuelles) – trop de partitions augmentent la surcharge des métadonnées et peuvent compliquer les plans. Les éléments uniques doivent contenir la colonne de partition ; j'en tiens compte dans le schéma.
Souvent, un simple processus d'archivage, qui déplace les données froides vers des tables d'archives allégées. L'espace de travail actif diminue, le pool de tampons est plus fréquent et, même sans partitionnement, la latence diminue. Pour les tables à forte charge d'écriture, je réduis les index secondaires superflus afin de limiter les coûts d'insertion et de mise à jour – chaque index supplémentaire est un chemin d'écriture supplémentaire.
Quand les infrastructures freinent
Même si les requêtes sont le levier principal, il arrive parfois que la Infrastructure le goulot d'étranglement. Je vérifie le CPU-Steal, élevé iowait, les latences de stockage et le RTT réseau. Les symptômes fréquents sont des lectures P95 de plusieurs millisecondes malgré de bons plans ou des latences fluctuantes sous charge. Je remédie à cela par la proximité (même AZ/VLAN), des connexions privées stables, un IOPS/débit suffisant et, si l'application et la base de données fonctionnent sur le même hôte, l'accès via des sockets Unix. Je m'épargne les handshakes TLS et la résolution DNS grâce à Keep-Alive et Connection Reuse. Le plus important reste : d'abord mesurer, puis modifier.
Contrôle pratique : seuils mesurables
Concret Seuils me facilitent la Définition des priorités. J'utilise l'aperçu suivant pour déterminer rapidement la situation et prendre des mesures ciblées.
| Cause | Indicateur typique | valeur seuil | Priorité | mesure immédiate |
|---|---|---|---|---|
| Base de données externe ou interne ? | Latence des requêtes | 0,0005 s interne / 0,02–0,06 s externe (source [1]) | Haut dans les applications de chat | Réduire les allers-retours, batching/JOINs |
| Indices manquants | Lignes examinées » Lignes envoyées | Facteur > 100 critique | Très élevé | Évaluer EXPLAIN, créer un index composite |
| Faible mémoire tampon | Taux d'accès au pool de tampons | < 95 % sur Hotset | Haute | Augmenter la taille du pool tampon, vérifier le working set |
| Modèle N+1 | Requêtes par demande | > 20 pour les listes simples | Moyenne-haute | JOIN ou IN au lieu de requêtes consécutives |
| Établissement de la connexion | Temps de connexion | P95 > 30 ms | Moyens | Activer le pooling, personnaliser Keep-Alive |
Plan d'action rapide
Je commence par les Indices et le Slow-Log: EXPLAIN, ajouter les clés manquantes, créer des conditions sargables. Ensuite, j'élimine N+1 et remplace les sous-sélections par des JOIN, éventuellement avec le traitement par lots. Dans un troisième temps, j'active le connection pooling et réduis les allers-retours grâce à des agrégations ciblées. J'optimise ensuite le buffer pool, vérifie le taux de réussite et transfère les lectures chaudes vers Redis. Pour des exemples pratiques supplémentaires, consultez Optimiser la base de données SQL avec des mesures immédiatement applicables.
Bref résumé
Une latence élevée de la base de données est généralement due à une faible Requêtes, et non par le Hébergement. Les indices, les JOIN propres, le pooling de connexions et un pool de tampons suffisamment grand sont déterminants. Il existe des différences de latence externes, mais elles perdent de leur importance si la conception de la requête est correcte. La surveillance contextuelle sépare la cause et l'effet et permet d'intervenir plus rapidement et de manière plus ciblée. En suivant cette séquence, vous réduisez durablement la latence, sans changer de fournisseur, mais avec une application nettement plus rapide.


