MariaDB
 sql >> Base de données >  >> RDS >> MariaDB

Maximiser l'efficacité des requêtes de base de données pour MySQL - Première partie

Les requêtes lentes, les requêtes inefficaces ou les requêtes longues sont des problèmes qui affectent régulièrement les DBA. Ils sont toujours omniprésents, mais font inévitablement partie de la vie de toute personne responsable de la gestion d'une base de données.

Une mauvaise conception de la base de données peut affecter l'efficacité de la requête et ses performances. Le manque de connaissances ou l'utilisation inappropriée des appels de fonction, des procédures stockées ou des routines peut également entraîner une dégradation des performances de la base de données et même nuire à l'ensemble du cluster de bases de données MySQL.

Pour une réplication maître-esclave, une cause très courante de ces problèmes sont les tables qui manquent d'index primaires ou secondaires. Cela provoque un retard de l'esclave qui peut durer très longtemps (dans le pire des cas).

Dans cette série de blogs en deux parties, nous vous donnerons un cours de rappel sur la façon d'aborder la maximisation de vos requêtes de base de données dans MySQL pour améliorer l'efficacité et les performances.

Ajoutez toujours un index unique à votre table

Les tables qui n'ont pas de clés primaires ou uniques créent généralement d'énormes problèmes lorsque les données deviennent plus volumineuses. Lorsque cela se produit, une simple modification des données peut bloquer la base de données. Le manque d'index appropriés et une instruction UPDATE ou DELETE a été appliquée à la table particulière, une analyse complète de la table sera choisie comme plan de requête par MySQL. Cela peut entraîner des E/S disque élevées pour les lectures et les écritures et dégrader les performances de votre base de données. Voir un exemple ci-dessous :

root[test]> show create table sbtest2\G

*************************** 1. row ***************************

       Table: sbtest2

Create Table: CREATE TABLE `sbtest2` (

  `id` int(10) unsigned NOT NULL,

  `k` int(10) unsigned NOT NULL DEFAULT '0',

  `c` char(120) NOT NULL DEFAULT '',

  `pad` char(60) NOT NULL DEFAULT ''

) ENGINE=InnoDB DEFAULT CHARSET=latin1

1 row in set (0.00 sec)



root[test]> explain extended update sbtest2 set k=52, pad="xx234xh1jdkHdj234" where id=57;

+----+-------------+---------+------------+------+---------------+------+---------+------+---------+----------+-------------+

| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref | rows | filtered | Extra       |

+----+-------------+---------+------------+------+---------------+------+---------+------+---------+----------+-------------+

|  1 | UPDATE      | sbtest2 | NULL       | ALL | NULL | NULL | NULL    | NULL | 1923216 | 100.00 | Using where |

+----+-------------+---------+------------+------+---------------+------+---------+------+---------+----------+-------------+

1 row in set, 1 warning (0.06 sec)

Alors qu'une table avec clé primaire a un très bon plan de requête,

root[test]> show create table sbtest3\G

*************************** 1. row ***************************

       Table: sbtest3

Create Table: CREATE TABLE `sbtest3` (

  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,

  `k` int(10) unsigned NOT NULL DEFAULT '0',

  `c` char(120) NOT NULL DEFAULT '',

  `pad` char(60) NOT NULL DEFAULT '',

  PRIMARY KEY (`id`),

  KEY `k` (`k`)

) ENGINE=InnoDB AUTO_INCREMENT=2097121 DEFAULT CHARSET=latin1

1 row in set (0.00 sec)



root[test]> explain extended update sbtest3 set k=52, pad="xx234xh1jdkHdj234" where id=57;

+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+

| id | select_type | table   | partitions | type | possible_keys | key     | key_len | ref | rows | filtered | Extra   |

+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+

|  1 | UPDATE      | sbtest3 | NULL       | range | PRIMARY | PRIMARY | 4       | const | 1 | 100.00 | Using where |

+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+

1 row in set, 1 warning (0.00 sec)

Les clés primaires ou uniques fournissent un composant vital pour une structure de table car cela est très important, en particulier lors de la maintenance d'une table. Par exemple, l'utilisation d'outils du Percona Toolkit (tels que pt-online-schema-change ou pt-table-sync) recommande que vous ayez des clés uniques. Gardez à l'esprit que la PRIMARY KEY est déjà une clé unique et qu'une clé primaire ne peut pas contenir de valeurs NULL mais une clé unique. L'attribution d'une valeur NULL à une clé primaire peut provoquer une erreur du type :

ERROR 1171 (42000): All parts of a PRIMARY KEY must be NOT NULL; if you need NULL in a key, use UNIQUE instead

Pour les nœuds esclaves, il est également courant qu'à certaines occasions, la clé primaire/unique ne soit pas présente sur la table, ce qui constitue donc une divergence de la structure de la table. Vous pouvez utiliser mysqldiff pour y parvenir ou vous pouvez mysqldump --no-data … params et exécuter un diff pour comparer sa structure de table et vérifier s'il y a une différence.

Analyser les tables avec des index en double, puis les supprimer

Les index en double peuvent également entraîner une dégradation des performances, en particulier lorsque la table contient un grand nombre d'enregistrements. MySQL doit effectuer plusieurs tentatives pour optimiser la requête et effectuer plus de plans de requête à vérifier. Cela inclut l'analyse de la distribution ou des statistiques d'index volumineux, ce qui augmente les performances, car cela peut entraîner une contention de la mémoire ou une utilisation élevée de la mémoire d'E/S.

La dégradation des requêtes lorsque des index en double sont observés sur une table est également attribuée à la saturation du pool de mémoire tampon. Cela peut également affecter les performances de MySQL lorsque le point de contrôle vide les journaux de transactions sur le disque. Cela est dû au traitement et au stockage d'un index indésirable (qui est en fait un gaspillage d'espace dans l'espace table particulier de cette table). Notez que les index en double sont également stockés dans l'espace de table qui doit également être stocké dans le pool de mémoire tampon.

Jetez un œil au tableau ci-dessous qui contient plusieurs clés en double :

root[test]#> show create table sbtest3\G

*************************** 1. row ***************************

       Table: sbtest3

Create Table: CREATE TABLE `sbtest3` (

  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,

  `k` int(10) unsigned NOT NULL DEFAULT '0',

  `c` char(120) NOT NULL DEFAULT '',

  `pad` char(60) NOT NULL DEFAULT '',

  PRIMARY KEY (`id`),

  KEY `k` (`k`,`pad`,`c`),

  KEY `kcp2` (`id`,`k`,`c`,`pad`),

  KEY `kcp` (`k`,`c`,`pad`),

  KEY `pck` (`pad`,`c`,`id`,`k`)

) ENGINE=InnoDB AUTO_INCREMENT=2048561 DEFAULT CHARSET=latin1

1 row in set (0.00 sec)

et a une taille de 2,3 Gio

root[test]#> \! du -hs /var/lib/mysql/test/sbtest3.ibd

2.3G    /var/lib/mysql/test/sbtest3.ibd

Laissons tomber les index en double et reconstruisons la table avec une modification sans opération,

root[test]#> drop index kcp2 on sbtest3; drop index kcp on sbtest3 drop index pck on sbtest3;

Query OK, 0 rows affected (0.01 sec)

Records: 0  Duplicates: 0  Warnings: 0

Query OK, 0 rows affected (0.01 sec)

Records: 0  Duplicates: 0  Warnings: 0

Query OK, 0 rows affected (0.01 sec)

Records: 0  Duplicates: 0  Warnings: 0



root[test]#> alter table sbtest3 engine=innodb;

Query OK, 0 rows affected (28.23 sec)

Records: 0  Duplicates: 0  Warnings: 0



root[test]#> \! du -hs /var/lib/mysql/test/sbtest3.ibd

945M    /var/lib/mysql/test/sbtest3.ibd

Il a été en mesure d'économiser jusqu'à ~59 % de l'ancienne taille de l'espace de table, ce qui est vraiment énorme.

Pour déterminer les index en double, vous pouvez utiliser pt-duplicate-checker pour gérer le travail pour vous.

Optimisez votre pool de tampons

Pour cette section, je me réfère uniquement au moteur de stockage InnoDB.

Le pool de tampons est un composant important dans l'espace du noyau InnoDB. C'est là qu'InnoDB met en cache les données de table et d'index lors de l'accès. Il accélère le traitement car les données fréquemment utilisées sont stockées efficacement dans la mémoire à l'aide de BTREE. Par exemple, si vous avez plusieurs tables composées de>=100 Go et que vous y accédez de manière intensive, nous vous suggérons de déléguer une mémoire volatile rapide à partir d'une taille de 128 Go et de commencer à affecter le pool de mémoire tampon avec 80 % de la mémoire physique. Les 80% doivent être contrôlés efficacement. Vous pouvez utiliser SHOW ENGINE INNODB STATUS \G ou vous pouvez tirer parti d'un logiciel de surveillance tel que ClusterControl qui offre une surveillance fine comprenant un pool de mémoire tampon et ses mesures de santé pertinentes. Définissez également la variable innodb_buffer_pool_instances en conséquence. Vous pouvez définir une valeur supérieure à 8 (valeur par défaut si innodb_buffer_pool_size>=1 Go), comme 16, 24, 32 ou 64 ou plus si nécessaire.

Lors de la surveillance du pool de mémoire tampon, vous devez vérifier la variable d'état globale Innodb_buffer_pool_pages_free qui vous indique s'il est nécessaire d'ajuster le pool de mémoire tampon, ou peut-être déterminer s'il existe également des index indésirables ou en double qui consomment le amortir. Le SHOW ENGINE INNODB STATUS \G offre également un aspect plus détaillé des informations sur le pool de mémoire tampon, y compris son pool de mémoire tampon individuel basé sur le nombre d'innodb_buffer_pool_instances que vous avez défini.

Utiliser les index FULLTEXT (mais uniquement si applicable)

Utiliser des requêtes comme,

SELECT bookid, page, context FROM books WHERE context like '%for dummies%';

où le contexte est une colonne de type chaîne (char, varchar, texte), est un exemple d'une super mauvaise requête ! Tirer un contenu volumineux d'enregistrements avec un filtre qui doit être gourmand se termine par une analyse complète de la table, et c'est tout simplement fou. Envisagez d'utiliser l'index FULLTEXT. Les index FULLTEXT ont une conception d'index inversée. Les index inversés stockent une liste de mots et, pour chaque mot, une liste de documents dans lesquels le mot apparaît. Pour prendre en charge la recherche de proximité, les informations de position de chaque mot sont également stockées, sous forme de décalage d'octet.

Afin d'utiliser FULLTEXT pour rechercher ou filtrer des données, vous devez utiliser la combinaison de la syntaxe MATCH() ...AGAINST et non comme la requête ci-dessus. Bien sûr, vous devez spécifier le champ comme étant votre champ d'index FULLTEXT.

Pour créer un index FULLTEXT, spécifiez simplement avec FULLTEXT comme index. Voir l'exemple ci-dessous :

root[minime]#> CREATE FULLTEXT INDEX aboutme_fts ON users_info(aboutme);

Query OK, 0 rows affected, 1 warning (0.49 sec)

Records: 0  Duplicates: 0  Warnings: 1



root[jbmrcd_date]#> show warnings;

+---------+------+--------------------------------------------------+

| Level   | Code | Message                                          |

+---------+------+--------------------------------------------------+

| Warning |  124 | InnoDB rebuilding table to add column FTS_DOC_ID |

+---------+------+--------------------------------------------------+

1 row in set (0.00 sec)

Bien que l'utilisation d'index FULLTEXT puisse offrir des avantages lors de la recherche de mots dans un contexte très large à l'intérieur d'une colonne, elle crée également des problèmes lorsqu'elle est utilisée de manière incorrecte.

Lorsque vous effectuez une recherche FULLTEXT pour une table volumineuse consultée en permanence (où un certain nombre de requêtes client recherchent des mots clés différents et uniques), cela peut être très gourmand en ressources processeur.

Il y a aussi certaines occasions où FULLTEXT n'est pas applicable. Voir cet article de blog externe. Bien que je n'aie pas essayé cela avec 8.0, je ne vois aucun changement pertinent à cela. Nous vous suggérons de ne pas utiliser FULLTEXT pour effectuer des recherches dans un environnement Big Data, en particulier pour les tables à fort trafic. Sinon, essayez d'exploiter d'autres technologies telles qu'Apache Lucene, Apache Solr, tsearch2 ou Sphinx.

Évitez d'utiliser NULL dans les colonnes

Les colonnes contenant des valeurs nulles conviennent parfaitement à MySQL. Mais si vous utilisez des colonnes avec des valeurs nulles dans un index, cela peut affecter les performances des requêtes car l'optimiseur ne peut pas fournir le bon plan de requête en raison d'une mauvaise distribution de l'index. Cependant, il existe certaines façons d'optimiser les requêtes qui impliquent des valeurs nulles, mais bien sûr, si cela répond aux exigences. Veuillez consulter la documentation de MySQL sur l'optimisation nulle. Vous pouvez également consulter cette publication externe qui est également utile.

Concevez efficacement votre topologie de schéma et votre structure de tables

Dans une certaine mesure, la normalisation de vos tables de base de données de 1NF (première forme normale) à 3NF (troisième forme normale) vous offre un certain avantage pour l'efficacité des requêtes car les tables normalisées ont tendance à éviter les enregistrements redondants. Une planification et une conception appropriées de vos tables sont très importantes car c'est ainsi que vous récupérez ou extrayez des données et que chacune de ces actions a un coût. Avec les tables normalisées, l'objectif de la base de données est de garantir que chaque colonne non clé de chaque table dépend directement de la clé ; toute la clé et rien que la clé. Si cet objectif est atteint, il paie des avantages sous la forme de licenciements réduits, moins d'anomalies et une efficacité améliorée.

Bien que la normalisation de vos tables présente de nombreux avantages, cela ne signifie pas que vous devez normaliser toutes vos tables de cette manière. Vous pouvez implémenter une conception pour votre base de données à l'aide de Star Schema. La conception de vos tables à l'aide de Star Schema présente l'avantage de requêtes plus simples (évite les jointures croisées complexes), la facilité de récupération des données pour les rapports, offre des gains de performances car il n'est pas nécessaire d'utiliser des unions ou des jointures complexes, ou des agrégations rapides. Un schéma en étoile est simple à mettre en œuvre, mais vous devez planifier avec soin car il peut créer de gros problèmes et inconvénients lorsque votre table devient plus grande et nécessite une maintenance. Star Schema (et ses tables sous-jacentes) sont sujets à des problèmes d'intégrité des données, vous pouvez donc avoir une forte probabilité que certaines de vos données soient redondantes. Si vous pensez que cette table doit être constante (structure et conception) et est conçue pour utiliser l'efficacité des requêtes, alors c'est un cas idéal pour cette approche.

Mélanger vos conceptions de base de données (tant que vous êtes en mesure de déterminer et d'identifier le type de données à extraire de vos tables) est très important car vous pouvez bénéficier de requêtes plus efficaces et ainsi que aider le DBA avec les sauvegardes, la maintenance et la restauration.

Débarrassez-vous des données constantes et anciennes

Nous avons récemment rédigé quelques bonnes pratiques pour l'archivage de votre base de données dans le cloud. Il explique comment vous pouvez tirer parti de l'archivage des données avant qu'elles ne soient transférées dans le cloud. Alors, comment se débarrasser des anciennes données ou archiver vos données constantes et anciennes contribue-t-il à l'efficacité des requêtes ? Comme indiqué dans mon blog précédent, il y a des avantages pour les grandes tables qui sont constamment modifiées et insérées avec de nouvelles données, l'espace de table peut se développer rapidement. MySQL et InnoDB fonctionnent efficacement lorsque les enregistrements ou les données sont contigus les uns aux autres et ont une signification pour la ligne suivante de la table. Cela signifie que si vous n'avez pas d'anciens enregistrements qui ne doivent plus être utilisés, l'optimiseur n'a pas besoin de l'inclure dans les statistiques offrant un résultat beaucoup plus efficace. C'est logique, non ? Et aussi, l'efficacité des requêtes n'est pas seulement du côté de l'application, elle doit également tenir compte de son efficacité lors de l'exécution d'une sauvegarde et lors d'une maintenance ou d'un basculement. Par exemple, si vous avez une mauvaise et longue requête qui peut affecter votre période de maintenance ou un basculement, cela peut être un problème.

Activer la journalisation des requêtes selon les besoins

Configurez toujours le journal des requêtes lentes de MySQL en fonction de vos besoins personnalisés. Si vous utilisez Percona Server, vous pouvez tirer parti de leur journalisation étendue des requêtes lentes. Il vous permet de définir habituellement certaines variables. Vous pouvez filtrer les types de requêtes en combinaison, telles que full_scan, full_join, tmp_table, etc. Vous pouvez également dicter le taux de journalisation des requêtes lentes via la variable log_slow_rate_type, et bien d'autres.

L'importance d'activer la journalisation des requêtes dans MySQL (telles que les requêtes lentes) est bénéfique pour inspecter vos requêtes afin que vous puissiez optimiser ou régler votre MySQL en ajustant certaines variables adaptées à vos besoins. Pour activer le journal des requêtes lentes, assurez-vous que ces variables sont configurées :

  • long_query_time - attribuez la bonne valeur pour la durée des requêtes. Si les requêtes prennent plus de 10 secondes (par défaut), elles tomberont dans le fichier journal des requêtes lentes que vous avez attribué.
  • slow_query_log - pour l'activer, définissez-le sur 1.
  • slow_query_log_file :il s'agit du chemin de destination de votre fichier journal des requêtes lentes.

Le journal des requêtes lentes est très utile pour l'analyse des requêtes et le diagnostic des requêtes incorrectes qui provoquent des blocages, des retards d'esclaves, des requêtes longues, une utilisation intensive de la mémoire ou du processeur, ou même une panne du serveur. Si vous utilisez pt-query-digest ou pt-index-usage, utilisez le fichier journal des requêtes lentes comme cible source pour signaler ces requêtes de la même manière.

Conclusion

Dans ce blog, nous avons discuté de certaines façons d'optimiser l'efficacité des requêtes de base de données. Dans cette prochaine partie, nous discuterons d'encore plus de facteurs qui peuvent vous aider à maximiser les performances. Restez à l'écoute !