Aperçu
Après des années de développement, MySQL est devenue la base de données la plus populaire, largement utilisée dans l'industrie Internet et pénétrant progressivement dans diverses industries traditionnelles. La raison de sa popularité réside, d’une part, dans ses excellentes capacités de traitement de transactions à haute concurrence, et d’autre part, il bénéficie également du riche écosystème de MySQL. MySQL fonctionne bien dans le traitement des requêtes courtes dans les scénarios OLTP, mais sa capacité à gérer des requêtes complexes et volumineuses est limitée. Le point le plus direct est que pour une instruction SQL, MySQL ne peut utiliser qu'un seul cœur de processeur pour la traiter. Dans ce scénario, il ne peut pas utiliser les capacités multicœurs du processeur hôte. MySQL n'est pas resté immobile et s'est développé. La version 8.0.14 nouvellement lancée introduit pour la première fois la fonctionnalité de requête parallèle, qui double les performances des instructions de type check table et select count(*). Même si les scénarios d’utilisation actuels sont relativement limités, les développements ultérieurs méritent d’être attendus avec impatience.
Recommandation : "Tutoriel vidéo mysql"
Comment utiliser
Définir le nombre de threads simultanés en configurant le paramètre innodb_parallel_read_threads. Peut démarrer la fonction d'analyse parallèle, la valeur par défaut est 4. Je vais faire une expérience simple ici, importer 200 millions de données via sysbench, configurer innodb_parallel_read_threads
sur 1, 2, 4, 8, 16, 32, 64 respectivement pour tester l'effet de l'exécution parallèle. L'instruction de test est select count(*) from sbtest1;
L'axe horizontal est le nombre de threads simultanés configurés et l'axe vertical est le temps d'exécution de l'instruction. À en juger par les résultats des tests, les performances parallèles globales sont toujours bonnes. L'analyse de 200 millions d'enregistrements est passée de 18 secondes pour un seul thread à 1 seconde pour 32 threads. Quel que soit le développement de la concurrence à l'avenir, en raison de la quantité limitée de données, la consommation de gestion des multi-threads dépasse l'amélioration des performances apportée par la concurrence, et le temps d'exécution SQL ne peut pas continuer à être raccourci.
Exécution parallèle de MySQL
En fait, l'exécution parallèle actuelle de MySQL en est encore à ses débuts, comme le montre la figure ci-dessous. Traitement en série MySQL précédent d'un seul formulaire SQL ; Celui du milieu est la capacité parallèle fournie par la version actuelle de MySQL, la forme d'analyse parallèle du moteur InnoDB ; celle à l'extrême droite est la forme que MySQL développera à l'avenir. L'optimiseur génère un plan parallèle basé sur la charge du système et SQL, et envoie le plan de partition à l'exécuteur pour la mise en œuvre de la parallélisation. L'exécution parallèle n'est pas seulement une analyse parallèle, mais inclut également l'agrégation parallèle, la jointure parallèle, le regroupement parallèle et le tri parallèle. Il n'y a aucune modification prise en charge dans l'optimiseur et l'exécuteur de niveau supérieur de la version actuelle de MySQL. Par conséquent, la discussion suivante se concentre principalement sur la façon dont le moteur InnoDB implémente l'analyse parallèle, y compris principalement le partitionnement, l'analyse parallèle, la lecture anticipée et les classes d'adaptateur qui interagissent avec l'exécuteur.
Partitionnement
Une étape essentielle de l'analyse parallèle est le partitionnement, qui divise les données numérisées en plusieurs parties afin que plusieurs threads puissent Balayage parallèle. Le moteur InnoDB est une table organisée en index. Les données sont stockées sur le disque sous la forme d'un arbre B+. L'unité d'un nœud est une page (bloc/page). En même temps, les pages chaudes sont mises en cache dans le pool de tampons. et éliminé grâce à l'algorithme LRU. La logique du partitionnement consiste à commencer à partir de la page du nœud racine et à analyser couche par couche. Lorsqu'il est déterminé que le nombre de branches sur une certaine couche dépasse le nombre de threads configuré, le fractionnement s'arrête. Lors de la mise en œuvre, un total de deux partitions seront effectivement effectuées.La première partition est basée sur le nombre de branches sur la page du nœud racine.L'enregistrement du nœud feuille le plus à gauche de chaque branche est la limite inférieure gauche, et cet enregistrement est enregistré. comme limite supérieure adjacente. La limite supérieure droite d'une branche. De cette façon, l'arbre B+ est divisé en plusieurs sous-arbres, et chaque sous-arbre est une partition d'analyse. Après la première partition, il peut y avoir un problème selon lequel le nombre de partitions ne peut pas utiliser pleinement le multicœur. Par exemple, si le thread d'analyse parallèle est configuré sur 3 et qu'après la première partition, 4 partitions sont générées, puis après la première partition. les 3 premières partitions sont complétées en parallèle, la quatrième. Chaque partition ne peut être analysée que par un seul thread au maximum, et l'effet final est que les ressources multicœurs ne peuvent pas être pleinement utilisées.
Partitionnement secondaire
Afin de résoudre ce problème, la version 8.0.17 a introduit le partitionnement secondaire Pour la quatrième partition, continuez à explorer la division, donc de nombreux sous-. les partitions peuvent être analysées simultanément, et la granularité minimale de l'analyse simultanée par le moteur InnoDB est le niveau de la page. La logique spécifique pour évaluer le partitionnement secondaire est qu'après le partitionnement principal, si le nombre de partitions est supérieur au nombre de threads, les partitions dont le nombre est supérieur au nombre de threads doivent être continuées pour le partitionnement secondaire si le nombre de partitions est élevé ; est inférieur au nombre de threads et le niveau B+tree est très profond, alors toutes les partitions nécessitent un partitionnement secondaire.
Le code pertinent est le suivant :
split_point = 0; if (ranges.size() > max_threads()) { //最后一批分区进行二次分区 split_point = (ranges.size() / max_threads()) * max_threads(); } else if (m_depth < SPLIT_THRESHOLD) { /* If the tree is not very deep then don't split. For smaller tables it is more expensive to split because we end up traversing more blocks*/ split_point = max_threads(); } else { //如果B+tree的层次很深(层数大于或等于3,数据量很大),则所有分区都需要进行二次分区 }
Qu'il s'agisse d'une partition principale ou d'une partition secondaire, la logique des limites de la partition est la même. L'enregistrement du nœud feuille le plus à gauche de chaque partition. est la limite inférieure gauche et enregistre cet enregistrement comme limite supérieure droite de la branche précédente adjacente. Cela garantit qu'il y a suffisamment de partitions, une granularité suffisamment fine et un parallélisme suffisant. La figure ci-dessous montre la configuration de 3 threads simultanés recherchant le partitionnement secondaire.
Le code pertinent est le suivant :
create_ranges(size_t depth, size_t level) 一次分区: parallel_check_table add_scan partition(scan_range, level=0) /* start at root-page */ create_ranges(scan_range, depth=0, level=0) create_contexts(range, index >= split_point) 二次分区: split() partition(scan_range, level=1) create_ranges(depth=0,level)
Scan parallèle
Après une partition, placez chaque tâche d'analyse de partition dans une file d'attente sans verrouillage. Le thread de travail parallèle obtient la tâche de la file d'attente et exécute la tâche d'analyse si la tâche obtenue a l'attribut split, à ce moment le travailleur Le. la tâche sera divisée deux fois et mise dans la file d'attente. Ce processus comprend principalement deux interfaces principales, l'une est l'interface du thread de travail et l'autre est l'interface d'enregistrement de parcours. La première obtient les tâches de la file d'attente et les exécute, et maintient des décomptes statistiques, la seconde obtient les enregistrements appropriés en fonction de la visibilité et des injections. via le traitement de la fonction de rappel de la couche supérieure, tel que le comptage, etc.
Parallel_reader::worker(size_t thread_id)
{
1. Extrayez la tâche ctx de la file d'attente ctx
2. Selon l'attribut split de ctx, Déterminez si la partition doit être divisée davantage (split())
3. Parcourez tous les enregistrements de la partition (traverse())
4. Une fois la tâche de partition terminée , maintenez le nombre m_n_completed
5. Si le nombre m_n_compeleted atteint le nombre ctx, réveillez tous les threads de travail et terminez
6. Renvoyez les informations d'erreur en fonction de l'interface de traversée.
}
Parallel_reader::Ctx::traverse()
{
1. Définissez le curseur en fonction de la plage
2. Trouvez btree, positionnez le curseur sur la position de départ de la plage
3. Déterminez la visibilité (check_visibility)
4. Si visible, calculez en fonction de la fonction de rappel (comme les statistiques)
5. Parcourez en arrière Si le dernier enregistrement de la page est atteint, démarrez le mécanisme de lecture anticipée (submit_read_ahead)
6. Terminez après avoir dépassé la plage
>
Dans le même temps, dans la version 8.0, .17 introduit également un mécanisme de lecture anticipée pour éviter le problème des mauvaises performances parallèles dues aux goulots d'étranglement des E/S. Actuellement, le nombre de threads pour la pré-lecture ne peut pas être configuré et est codé en dur sur 2 threads dans le code. L'unité de chaque pré-lecture est un cluster (les fichiers InnoDB sont gérés via une structure à trois niveaux de segments, clusters et pages. Un cluster est un groupe de pages consécutives), qui peut faire 1 Mo ou 2 Mo selon la taille de la configuration des pages. Pour une configuration courante de 16 000 pages, 1 Mo est prélu à chaque fois, soit 64 pages. Lorsque le thread de travail analyse, il déterminera d'abord si la page adjacente suivante est la première page du cluster. Si tel est le cas, il lancera une tâche de pré-lecture. Les tâches de lecture anticipée sont également mises en cache via la file d'attente sans verrouillage. Le thread de travail est le producteur et le travailleur de lecture anticipée est le consommateur. Puisque toutes les pages de partition ne se chevauchent pas, les tâches de lecture anticipée ne sont pas répétées.
Interaction de l'exécuteur (adaptateur)
En fait, MySQL a encapsulé une classe d'adaptateur Parallel_reader_adapter pour une utilisation dans la couche supérieure afin de préparer une exécution parallèle plus riche ultérieure. Tout d'abord, cette classe doit résoudre le problème du format d'enregistrement et convertir les enregistrements analysés par la couche moteur au format MySQL. De cette façon, les couches supérieure et inférieure sont découplées. L'exécuteur n'a pas besoin de détecter le format de la couche moteur. et est traité au format MySQL. L'ensemble du processus est une chaîne d'assemblage.Les enregistrements MySQL sont stockés par lots via un tampon. Le thread de travail lit en permanence les enregistrements de la couche moteur. En même temps, les enregistrements sont traités en continu par la couche supérieure. la vitesse peut être équilibrée via le tampon. Assurez-vous que l'ensemble du processus se déroule. La taille du cache par défaut est de 2 Mo. Le nombre d'enregistrements MySQL que le tampon peut mettre en cache est déterminé en fonction de la longueur des lignes d'enregistrement de la table. Le processus principal se trouve principalement dans l'interface process_rows. Le processus est le suivant
process_rows
{
1. Convertir les enregistrements du moteur en enregistrements MySQL
2. . Obtenez ce fil Informations sur le tampon (combien d'enregistrements MySQL ont été convertis et combien ont été envoyés à la couche supérieure)
3. Remplissez les enregistrements MySQL dans le tampon et incrémentez les statistiques m_n_read
4. .Appelez la fonction de rappel pour le traitement (comme les statistiques, l'agrégation, le tri, etc.), les statistiques d'incrémentation automatique m_n_send
>
Pour l'appelant, il est nécessaire de définir la méta-. Informations de la table et injecter la fonction de rappel d'enregistrement de traitement, telle que l'agrégation de traitement, le tri, le travail de groupe. La fonction de rappel est contrôlée en définissant m_init_fn, m_load_fn et m_end_fn.
Résumé
MySQL8.0 a introduit les requêtes parallèles Bien qu'elles soient encore relativement rudimentaires, elles nous ont déjà permis de voir le potentiel des requêtes parallèles MySQL. Nous l'avons vu lors de l'expérience. Une fois l'exécution parallèle activée, l'exécution des instructions SQL utilise pleinement les capacités multicœurs et le temps de réponse diminue fortement. Je pense que dans un avenir proche, la version 8.0 prendra en charge davantage d'opérateurs parallèles, notamment l'agrégation parallèle, la connexion parallèle, le regroupement parallèle et le tri parallèle.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!