Analyse du code source de l'index de texte intégral MySQL du processus d'exécution de l'instruction Insert

Cet article est partagé par Huawei Cloud Community « Analyse du code source de l'index de texte intégral MySQL : processus d'exécution de l'instruction d'insertion », auteur : base de données GaussDB.

0.PNG

1. Introduction générale

L'indexation en texte intégral est un moyen technique couramment utilisé dans le domaine de la recherche d'informations. Elle est utilisée pour les problèmes de recherche en texte intégral, c'est-à-dire la recherche de documents contenant le mot en fonction du mot. le navigateur, le moteur de recherche doit trouver tous les documents associés et triés par pertinence.

L'implémentation sous-jacente de l'index de texte intégral est basée sur un index inversé. L' index dit inversé décrit la relation de mappage entre les mots et les documents, exprimée sous la forme de (mot, (le document où se trouve le mot, le décalage du mot dans le document)). L'index de texte intégral est organisé :

mysql> CREATE TABLE opening_lines (
           id INT UNSIGNED AUTO_INCREMENT NOT NULL CLÉ PRIMAIRE,
           ouverture_ligne TEXTE(500),
           auteur VARCHAR(200),
           titre VARCHAR(200),
           TEXTE COMPLET idx (opening_line)
           ) MOTEUR=InnoDB;    
mysql> INSERT INTO opening_lines(opening_line,author,title) VALEURS
           ("Appelle-moi Ismaël.", "Herman Melville", "Moby-Dick"),
           ("Un cri traverse le ciel.", "Thomas Pynchon", "Gravity\'s Rainbow"),
           ("Je suis un homme invisible.", "Ralph Ellison", "Invisible Man"),
           (« Où maintenant ? Qui maintenant ? Quand maintenant ? », « Samuel Beckett », « L'Innommable ») ;      
mysql> SET GLOBAL innodb_ft_aux_table='test/opening_lines';
mysql> sélectionnez * dans information_schema.INNODB_FT_INDEX_TABLE ;
 +---------------+--------------+-------------+-------- ---+--------+----------+  
| MOT | FIRST_DOC_ID | LAST_DOC_ID | DOC_COUNT | ID_DOC | POSTE |  
+---------------+--------------+-------------+-------- ---+--------+----------+  
| à travers | 4 | 4 | 1 | 4 | 18 |  
| appeler | 3 | 3 | 1 | 3 | 0 |  
| vient | 4 | 4 | 1 | 4 | 12 |  
| invisibles | 5 | 5 | 1 | 5 | 8 |  
| Ismaël | 3 | 3 | 1 | 3 | 8 |  
| homme | 5 | 5 | 1 | 5 | 18 |  
| maintenant | 6 | 6 | 1 | 6 | 6 |  
| maintenant | 6 | 6 | 1 | 6 | 9 |  
| maintenant | 6 | 6 | 1 | 6 | 10 |  
| criant | 4 | 4 | 1 | 4 | 2 |  
| ciel | 4 | 4 | 1 | 4 | 29 |  
+---------------+--------------+-------------+-------- ---+--------+----------+

Comme ci-dessus, une table est créée et un index de texte intégral est établi sur la colonne opening_line. Prenons l'exemple de l'insertion de « Appelle-moi Ismaël ». « Appelle-moi Ismaël ». est un document avec un identifiant de 3. Lors de la création d'un index de texte intégral, le document sera divisé en 3 mots « appelle », « moi ». , 'ishmael', car 'me' est plus petit que la longueur minimale de mot définie pour ft_min_word_len(4) et est finalement ignoré, seuls 'call' et 'ishmael' seront enregistrés dans l'index de texte intégral, où le la position de départ de « call » est le 0ème caractère du document, le décalage est de 0, la position de départ de « ismael » est le 12ème caractère du document et le décalage est de 12.

Pour une introduction plus détaillée aux fonctions de l'index de texte intégral, veuillez vous référer au manuel de référence MySQL 8.0. Cet article analysera brièvement le processus d'exécution de l'instruction Insert à partir du niveau du code source.

2. Cache d'index de texte intégral

Ce qui est enregistré dans la table d'index de texte intégral est {mot, {ID du document, position d'occurrence}}, c'est-à-dire que pour insérer un document, il doit être segmenté en plusieurs {mots, {ID du document, position d'occurrence}} tels que une structure, si à chaque fois Si le disque est vidé immédiatement après la segmentation des mots, les performances seront très mauvaises.

Afin d'atténuer ce problème, Innodb a introduit un cache d'index en texte intégral, qui fonctionne de manière similaire à Change Buffer. Chaque fois qu'un document est inséré, les résultats de la segmentation de mots sont d'abord mis en cache dans le cache, puis vidés sur le disque par lots lorsque le cache est plein, évitant ainsi des vidages fréquents du disque. Innodb définit la structure fts_cache_t pour gérer le cache, comme le montre la figure suivante :

1.png

Chaque table conserve un cache et pour chaque table pour laquelle un index de texte intégral est créé, un objet fts_cache_t est créé en mémoire. Notez que fts_cache_t est un cache au niveau de la table. Si plusieurs index de texte intégral sont créés pour une table, il y aura toujours un objet fts_cache_t correspondant dans la mémoire. Certains membres importants de fts_cache_t sont les suivants :

  • optimise_lock, delete_lock, doc_id_lock : verrous mutex, liés aux opérations simultanées.
  • delete_doc_ids : type de vecteur, stocke les doc_ids supprimés.
  • index : type vectoriel, chaque élément représente un index de texte intégral. Chaque fois qu'un index de texte intégral est créé, un élément sera ajouté au tableau. Les résultats de segmentation de mots de chaque index sont stockés dans une arborescence rouge-noir. La clé est le mot et la valeur est doc_id et le décalage du mot.
  • total_size : Toute la mémoire allouée par le cache, y compris la mémoire utilisée par ses sous-structures.

3. Insérer le processus d'exécution de l'instruction

En prenant le code source MySQL 8.0.22 comme exemple, l'exécution de l'instruction Insert est principalement divisée en trois étapes, à savoir l'étape d'écriture des enregistrements de ligne, l'étape de soumission de transaction et l'étape de nettoyage sale.

3.1 Phase d'écriture de l'enregistrement de ligne

Le flux de travail principal pour l'écriture des enregistrements de ligne est illustré dans la figure ci-dessous :

2.png

Comme le montre la figure ci-dessus, la chose la plus importante à ce stade est de générer le doc_id, de l'écrire dans l'enregistrement de ligne Innodb et de mettre en cache le doc_id afin que le contenu du texte puisse être obtenu en fonction du doc_id pendant la phase de soumission de la transaction. la pile d'appels de fonction est la suivante :

ha_innobase ::write_row
        ->row_insert_for_mysql
            ->row_insert_for_mysql_using_ins_graph
                ->row_mysql_convert_row_to_innobase
                    ->fts_create_doc_id
                        ->fts_get_next_doc_id
                ->fts_trx_add_op
                    ->fts_trx_table_add_op

fts_get_next_doc_id et fts_trx_table_add_op sont deux fonctions importantes. fts_get_next_doc_id permet d'obtenir les enregistrements de ligne doc_id contenant des colonnes cachées, telles que row_id, trx_id, etc. Si un index de texte intégral est créé, un champ caché FTS_DOC_ID sera également ajouté à la ligne. records. , cette valeur est obtenue dans fts_get_next_doc_id, comme suit :

Et fts_trx_add_op ajoute l'opération d'indexation en texte intégral à trx et sera traitée ultérieurement lorsque la transaction sera validée.

3.2 Phase de soumission des transactions

Le flux de travail principal de la phase de soumission des transactions est illustré dans la figure ci-dessous :

3.png

Cette étape est l'étape la plus importante de toute l'insertion FTS du document, l'obtention de {word, {document ID, occurrence position}} et l'insertion dans le cache sont toutes terminées à ce stade. Sa pile d'appels de fonction est la suivante :

fts_commit_table
      ->fts_add
          ->fts_add_doc_by_id
              ->fts_cache_add_doc
                    // Récupère le document basé sur doc_id et segmente le document en mots
                  ->fts_fetch_doc_from_rec
                    // Ajoute les résultats de la segmentation de mots au cache
                  ->fts_cache_add_doc
              ->fts_optimize_request_sync_table
                    //Créer un message FTS_MSG_SYNC_TABLE pour avertir le thread sale de nettoyer le thread sale
                  ->fts_optimize_create_msg(FTS_MSG_SYNC_TABLE)

Parmi elles, fts_add_doc_by_id est une fonction clé. Cette fonction accomplit principalement les choses suivantes :

1) Recherchez l'enregistrement de ligne basé sur doc_id et obtenez le document correspondant ;

2) Effectuer une segmentation de mots sur le document, obtenir la paire associée {mot, (le document où se trouve le mot, le décalage du mot dans le document)} et l'ajouter au cache
3) Déterminer si le cache-> ; total_size atteint le seuil, s'il atteint le seuil, ajoutez un message FTS_MSG_SYNC_TABLE à la file d'attente des messages du thread sale pour notifier au thread de vider (fts_optimize_create_msg). Le code spécifique est le suivant :

Pour faciliter la compréhension, j'ai omis la partie gestion des exceptions du code et certaines parties communes de recherche d'enregistrements, et j'ai donné de brefs commentaires :

ulint statique fts_add_doc_by_id(fts_trx_table_t *ftt, doc_id_t doc_id)
    {
            /* 1. Rechercher des enregistrements dans l'index fts_doc_id_index basé sur docid*/
          /* btr_cur_search_to_nth_level, btr_cur_search_to_nth_level sera appelé dans la fonction btr_pcur_open_with_no_init
            Le processus de recherche d'enregistrement dans l'arborescence b+ sera effectué. Tout d'abord, le nœud feuille où se trouve l'enregistrement docid est trouvé à partir du nœud racine, puis l'enregistrement docid est trouvé via une recherche binaire. */
        btr_pcur_open_with_no_init(fts_id_index, tuple, PAGE_CUR_LE,
                                    BTR_SEARCH_LEAF, &pcur, 0, &mtr);
        if (btr_pcur_get_low_match(&pcur) == 1) { /* Si un enregistrement docid est trouvé*/
            si (is_id_cluster) {
                 /** 1.1 Si fts_doc_id_index est un index clusterisé, cela signifie que les données de l'enregistrement de ligne ont été trouvées et que l'enregistrement de ligne est enregistré directement**/
                doc_pcur = &pcur;
              } autre {
                /** 1.2 Si fts_doc_id_index est un index secondaire, vous devez rechercher davantage l'enregistrement de ligne sur l'index clusterisé en fonction de l'identifiant de clé primaire trouvé dans 1.1 et enregistrer l'enregistrement de ligne après l'avoir trouvé **/ btr_pcur_open_with_no_init(clust_index, clust_ref, PAGE_CUR_LE,
                                           BTR_SEARCH_LEAF, &clust_pcur, 0, &mtr);
               doc_pcur = &clust_pcur;
             } // Traverser le cache->get_docs
            pour (ulint i = 0; i < num_idx; ++i) {
                /***** 2. Effectuez une segmentation de mots sur le document, obtenez la paire associée {mot, (le document où se trouve le mot, le décalage du mot dans le document)} et ajoutez-la au cache** ***/
                fts_doc_t doc;
                fts_doc_init(&doc);
        /** 2.1 Obtenez le document de contenu de la colonne correspondante de l'index de texte intégral dans l'enregistrement de ligne en fonction du doc_id et analysez le document, principalement pour construire les jetons de la structure fts_doc_t. Les jetons sont un arbre rouge-noir. structure. Chaque élément est un {mot, [la structure de la position où le mot apparaît dans le document]}, et les résultats de l'analyse sont stockés dans le doc**/.
                fts_fetch_doc_from_rec(ftt->fts_trx->trx, get_doc, clust_index,doc_pcur, offsets, &doc);
                /** 2.2 Ajoutez le {mot, [la position où le mot apparaît dans le document]} obtenu à l'étape 2.1 à index_cache**/
                fts_cache_add_doc(table->fts->cache, get_doc->index_cache, doc_id, doc.tokens);
               /***** 3. Déterminez si cache->total_size atteint le seuil. Si le seuil est atteint, ajoutez un message FTS_MSG_SYNC_TABLE à la file d'attente des messages du thread sale pour notifier au thread de nettoyer *****/
                bool need_sync = false ;
                si ((cache->total_size - cache->total_size_before_sync >
                     fts_max_cache_size / 10 || fts_need_sync) &&!cache->sync->in_progress) {
                  /** 3.1 Déterminer si le seuil est atteint**/
                  need_sync = vrai ;
                  cache->total_size_before_sync = cache->total_size;
                }
                    si (need_sync) {
                    /** 3.2 Packagez le message FTS_MSG_SYNC_TABLE et montez-le dans la file d'attente fts_optimize_wq, et informez le thread fts_optimize_thread de le nettoyer. Le contenu du message est l'ID de table **/ fts_optimize_request_sync_table(table);
                }
            }
        }
    }

Après avoir compris le processus ci-dessus, vous pouvez expliquer le phénomène particulier de soumission de transactions d'index en texte intégral décrit sur le site officiel. Reportez-vous à la section Gestion des transactions d'index en texte intégral InnoDB du manuel de référence MySQL 8.0 si vous insérez des enregistrements de ligne dans. Dans la table d'index de texte intégral, si la transaction en cours n'est pas validée, nous ne pouvons pas trouver l'enregistrement de ligne inséré via l'index de texte intégral dans la transaction en cours. La raison en est que la mise à jour de l'index de texte intégral est terminée lorsque la transaction est validée. Lorsque la transaction n'est pas validée, fts_add_doc_by_id n'a pas encore été exécutée. Par conséquent, l'enregistrement ne peut pas être trouvé via l'index de texte intégral. Cependant, à partir de la section 3.1, nous pouvons savoir que l'enregistrement de ligne Innodb a été inséré à ce moment-là. Si vous effectuez une requête via l'index de texte intégral, vous pouvez trouver l'enregistrement en exécutant directement SELECT COUNT(*) FROM opening_lines.

3.3 Étape de brossage

Le flux de travail principal de la phase de nettoyage est illustré dans la figure ci-dessous :

4.png

Lorsque InnoDB démarre, un thread d'arrière-plan sera créé, la fonction du thread est fts_optimize_thread et la file d'attente de travail est fts_optimize_wq. Dans la phase de soumission de transaction de la section 3.2, lorsque le cache est plein, la fonction fts_optimize_request_sync_table ajoutera un message FTS_MSG_SYNC_TABLE à la file d'attente fts_optimize_wq. Le thread d'arrière-plan supprimera le message et videra le cache sur le disque. Sa pile d'appels de fonction est la suivante :

 

fts_optimize_thread
        ->ib_wqueue_timedwait
            ->fts_optimize_sync_table
                ->fts_sync_table
                    ->fts_sync
                        ->fts_sync_commit
                            ->fts_cache_clear

Les principales opérations effectuées par ce thread sont les suivantes :

  1. Obtenez un message de la file d'attente fts_optimize_wq ;
  2. Déterminez le type de message S'il s'agit de FTS_MSG_SYNC_TABLE, effectuez un vidage ;
  3. Vider le contenu du cache vers la table auxiliaire sur le disque ;
  4. Videz le cache et remettez le cache à son état initial ;
  5. Revenez à l'étape 1 et recevez le message suivant ;

Dans la section 3.2, lorsque la transaction est soumise, si la taille totale du cache fts est supérieure au seuil de taille de mémoire défini, un FTS_MSG_SYNC_TABLE sera écrit et inséré dans la file d'attente fts_optimize_wq. Le thread sale traitera le message et videra les données. le cache fts sur le disque, puis videz le cache.

Il convient de mentionner que lorsque la taille totale du cache fts est supérieure au seuil de taille de mémoire défini, un seul message sera écrit dans la file d'attente fts_optimize_wq. À ce stade, le cache fts peut toujours écrire des données et de la mémoire avant qu'elles ne soient traitées par le cache fts. le thread de vidage en arrière-plan continuera d'augmenter, ce qui est également la cause première du problème de MOO qui provoque l'insertion simultanée d'index de texte intégral. Le correctif pour le problème est le correctif n° 32831765. LE SERVEUR AFFECTE LA CONDITION DU MOO LORS DU CHARGEMENT DE DEUX INNODB Lecteurs intéressés. peuvent le vérifier par eux-mêmes. 

Lien de vérification du MOO : https://bugs.mysql.com/bug.php?id=103523

Si le thread sale n'a pas encore sali le cache fts d'une certaine table, le processus MySQL plantera et les données du cache seront perdues. Après le redémarrage, la première fois que insert ou select est exécuté sur la table, les données dans le cache avant le crash seront restaurées dans la fonction fts_init_index. À ce moment, le synced_doc_id qui a été déposé sur le disque sera lu à partir de la configuration. table, et le synced_doc_id dans le tableau sera plus grand que synced_doc_id. Les enregistrements sont lus et segmentés en mots et restaurés dans le cache. Pour une implémentation spécifique, veuillez vous référer aux fonctions fts_doc_fetch_by_doc_id et fts_init_recover_doc.

Cliquez pour suivre et découvrir les nouvelles technologies de Huawei Cloud dès que possible~

 

Les ressources piratées de "Celebrating More Than Years 2" ont été téléchargées sur npm, obligeant npmmirror à suspendre le service unpkg. L'équipe chinoise d' IA de Microsoft a fait ses valises et s'est rendue aux États-Unis, impliquant des centaines de personnes. La bibliothèque de visualisation frontale et le projet open source bien connu de Baidu, ECharts - "aller à la mer" pour soutenir les escrocs Fish ont utilisé TeamViewer pour transférer 3,98 millions ! Que doivent faire les fournisseurs de postes de travail à distance ? Zhou Hongyi : Il ne reste plus beaucoup de temps à Google. Il est recommandé que tous les produits soient open source. Un ancien employé d'une société open source bien connue a annoncé la nouvelle : après avoir été interpellé par ses subordonnés, le responsable technique est devenu furieux et. a licencié l'employée enceinte. Google a montré comment exécuter ChromeOS sur une machine virtuelle Android. Veuillez me donner quelques conseils, quel rôle joue ici time.sleep(6). Microsoft réagit aux rumeurs selon lesquelles l'équipe chinoise d'IA "fait ses valises pour les États-Unis" Le Quotidien du Peuple commente en ligne la charge de type matriochka des logiciels de bureau : Ce n'est qu'en résolvant activement les "ensembles" que nous pourrons avoir un avenir
{{o.name}}
{{m.nom}}

Je suppose que tu aimes

Origine my.oschina.net/u/4526289/blog/11179935
conseillé
Classement