Analyse des MySQL-Volltextindex-Quellcodes des Ausführungsprozesses der Insert-Anweisung

Dieser Artikel wurde von der Huawei Cloud Community geteilt. „ Analyse des MySQL-Volltextindex-Quellcodes: Ausführungsprozess der Anweisung einfügen “, Autor: GaussDB-Datenbank.

0.PNG

1. Hintergrundeinführung

Die Volltextindizierung ist ein häufig verwendetes technisches Mittel im Bereich der Informationsbeschaffung. Sie wird für die Suche nach Dokumenten verwendet, die das Wort basierend auf dem Wort enthalten Der Browser muss alle zugehörigen Dokumente finden und nach Relevanz sortieren.

Die zugrunde liegende Implementierung des Volltextindex basiert auf einem invertierten Index. Der sogenannte invertierte Index beschreibt die Zuordnungsbeziehung zwischen Wörtern und Dokumenten, ausgedrückt in der Form (Wort, (das Dokument, in dem sich das Wort befindet, der Versatz des Wortes im Dokument)). Der Volltextindex ist wie folgt organisiert:

mysql> CREATE TABLE Opening_lines (
           id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
           Opening_line TEXT(500),
           Autor VARCHAR(200),
           Titel VARCHAR(200),
           VOLLSTÄNDIGER TEXT idx (opening_line)
           ) ENGINE=InnoDB;    
mysql> INSERT INTO Opening_lines(opening_line,author,title) VALUES
           („Nenn mich Ishmael.“, „Herman Melville“, „Moby-Dick“),
           („Ein Schrei kommt über den Himmel.“, „Thomas Pynchon“, „Gravity\'s Rainbow“),
           („Ich bin ein unsichtbarer Mann.“, „Ralph Ellison“, „Unsichtbarer Mann“),
           („Wo jetzt? Wer jetzt? Wann jetzt?“, „Samuel Beckett“, „The Unnamable“);      
mysql> SET GLOBAL innodb_ft_aux_table='test/opening_lines';
mysql> select * from information_schema.INNODB_FT_INDEX_TABLE;
 +-----------+--------------+-------------+-------- ---+--------+----------+  
| WORT | FIRST_DOC_ID | LAST_DOC_ID | DOC_COUNT | DOC_ID | POSITION |  
+-----------+--------------+-------------+-------- ---+--------+----------+  
| quer | 4 | 4 | 1 | 4 | 18 |  
| Anruf | 3 | 3 | 1 | 3 | 0 |  
| kommt | 4 | 4 | 1 | 4 | 12 |  
| unsichtbar | 5 | 5 | 1 | 5 | 8 |  
| Ismael | 3 | 3 | 1 | 3 | 8 |  
| Mann | 5 | 5 | 1 | 5 | 18 |  
| jetzt | 6 | 6 | 1 | 6 | 6 |  
| jetzt | 6 | 6 | 1 | 6 | 9 |  
| jetzt | 6 | 6 | 1 | 6 | 10 |  
| schreiend | 4 | 4 | 1 | 4 | 2 |  
| Himmel | 4 | 4 | 1 | 4 | 29 |  
+-----------+--------------+-------------+-------- ---+--------+----------+

Wie oben wird eine Tabelle erstellt und ein Volltextindex für die Spalte „opening_line“ erstellt. Nehmen Sie als Beispiel das Einfügen von „Nenn mich Ishmael.“ ist ein Dokument mit der ID 3. Beim Erstellen eines Volltextindex wird das Dokument in die drei Wörter „Ruf mich an“ und „Ich“ unterteilt. , 'ishmael', da 'me' kleiner als die eingestellte Mindestwortlänge von ft_min_word_len(4) ist und verworfen wird. Am Ende werden nur 'call' und 'ishmael' im Volltextindex erfasst, wobei die Die Startposition von „call“ ist das 0. Zeichen im Dokument, der Offset ist 0, die Startposition von „ishmael“ ist das 12. Zeichen im Dokument und der Offset ist 12.

Eine ausführlichere Einführung in die Funktionen des Volltextindex finden Sie im MySQL 8.0-Referenzhandbuch. In diesem Artikel wird der Ausführungsprozess der Insert-Anweisung auf Quellcodeebene kurz analysiert.

2. Volltext-Index-Cache

Was in der Volltextindextabelle aufgezeichnet wird, ist {Wort, {Dokument-ID, Vorkommensposition}}. Das heißt, um ein Dokument einzufügen, muss es in mehrere {Wörter, {Dokument-ID, Vorkommensposition}} segmentiert werden, z Eine Struktur, wenn die Festplatte jedes Mal sofort nach der Wortsegmentierung geleert wird, ist die Leistung sehr schlecht.

Um dieses Problem zu lindern, hat Innodb einen Volltext-Index-Cache eingeführt, der ähnlich wie Change Buffer funktioniert. Jedes Mal, wenn ein Dokument eingefügt wird, werden die Ergebnisse der Wortsegmentierung zunächst im Cache zwischengespeichert und dann stapelweise auf die Festplatte geleert, wenn der Cache voll ist, wodurch häufiges Leeren der Festplatte vermieden wird. Innodb definiert die Struktur fts_cache_t zum Verwalten des Caches, wie in der folgenden Abbildung dargestellt:

1.png

Jede Tabelle verwaltet einen Cache, und für jede Tabelle, für die ein Volltextindex erstellt wird, wird ein fts_cache_t-Objekt im Speicher erstellt. Beachten Sie, dass es sich bei fts_cache_t um einen Cache auf Tabellenebene handelt. Wenn für eine Tabelle mehrere Volltextindizes erstellt werden, befindet sich weiterhin ein entsprechendes fts_cache_t-Objekt im Speicher. Einige wichtige Mitglieder von fts_cache_t sind wie folgt:

  • optimieren_lock, gelöschter_lock, doc_id_lock: Mutex-Sperren, bezogen auf gleichzeitige Vorgänge.
  • gelöschte_doc_ids: Vektortyp, speichert gelöschte doc_ids.
  • Indizes: Vektortyp, jedes Element stellt einen Volltextindex dar. Jedes Mal, wenn ein Volltextindex erstellt wird, wird ein Element zum Array hinzugefügt. Die Wortsegmentierungsergebnisse jedes Index werden in einer rot-schwarzen Baumstruktur gespeichert. Der Schlüssel ist Wort und der Wert ist doc_id und der Offset des Wortes.
  • total_size: Der gesamte vom Cache zugewiesene Speicher, einschließlich des von seinen Unterstrukturen verwendeten Speichers.

3. Fügen Sie den Anweisungsausführungsprozess ein

Am Beispiel des MySQL 8.0.22-Quellcodes ist die Ausführung der Insert-Anweisung hauptsächlich in drei Phasen unterteilt, nämlich die Phase des Schreibens von Zeilendatensätzen, die Phase der Transaktionsübermittlung und die Phase der schmutzigen Reinigung.

3.1 Zeilenaufzeichnungsphase schreiben

Der Hauptarbeitsablauf zum Schreiben von Zeilendatensätzen ist in der folgenden Abbildung dargestellt:

2.png

Wie in der Abbildung oben gezeigt, besteht das Wichtigste in dieser Phase darin, doc_id zu generieren, in den Innodb-Zeilendatensatz zu schreiben und die doc_id zwischenzuspeichern, damit der Textinhalt während der Transaktionsübermittlungsphase basierend auf der doc_id abgerufen werden kann Der Funktionsaufrufstapel lautet wie folgt:

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 und fts_trx_table_add_op sind zwei wichtige Funktionen, um doc_id zu erhalten. Zeilendatensätze enthalten einige versteckte Spalten, wie row_id, trx_id usw. Wenn ein Volltextindex erstellt wird, wird der Zeile auch ein verstecktes Feld FTS_DOC_ID hinzugefügt Records wird dieser Wert in fts_get_next_doc_id wie folgt abgerufen:

Und fts_trx_add_op fügt die Volltextindexoperation zu trx hinzu und wird weiter verarbeitet, wenn die Transaktion festgeschrieben wird.

3.2 Phase der Transaktionseinreichung

Der Hauptablauf der Transaktionseinreichungsphase ist in der folgenden Abbildung dargestellt:

3.png

Diese Phase ist der wichtigste Schritt bei der gesamten FTS-Einfügung des Dokuments. Das Erhalten von {Wort, {Dokument-ID, Vorkommensposition}} und das Einfügen in den Cache sind in dieser Phase abgeschlossen. Der Funktionsaufrufstapel lautet wie folgt:

fts_commit_table
      ->fts_add
          ->fts_add_doc_by_id
              ->fts_cache_add_doc
                    // Holen Sie sich das Dokument basierend auf doc_id und segmentieren Sie das Dokument in Wörter
                  ->fts_fetch_doc_from_rec
                    // Fügen Sie die Ergebnisse der Wortsegmentierung zum Cache hinzu
                  ->fts_cache_add_doc
              ->fts_optimize_request_sync_table
                    // FTS_MSG_SYNC_TABLE-Nachricht erstellen, um den Dirty-Thread zu benachrichtigen, den Dirty-Thread zu bereinigen
                  ->fts_optimize_create_msg(FTS_MSG_SYNC_TABLE)

Unter diesen ist fts_add_doc_by_id eine Schlüsselfunktion. Diese Funktion erfüllt hauptsächlich die folgenden Aufgaben:

1) Suchen Sie den Zeilendatensatz basierend auf doc_id und rufen Sie das entsprechende Dokument ab.

2) Führen Sie eine Wortsegmentierung für das Dokument durch, erhalten Sie das zugehörige Paar {Wort (das Dokument, in dem sich das Wort befindet, der Versatz des Wortes im Dokument)} und fügen Sie es dem Cache hinzu.
3) Bestimmen Sie, ob Cache-> total_size erreicht den Schwellenwert. Wenn der Schwellenwert erreicht ist, fügen Sie eine FTS_MSG_SYNC_TABLE-Nachricht zur Nachrichtenwarteschlange des schmutzigen Threads hinzu, um den Thread zum Leeren zu benachrichtigen (fts_optimize_create_msg). Der spezifische Code lautet wie folgt:

Um das Verständnis zu erleichtern, habe ich den Teil des Codes zur Ausnahmebehandlung und einige allgemeine Teile zum Suchen von Datensätzen weggelassen und kurze Kommentare gegeben:

static ulint fts_add_doc_by_id(fts_trx_table_t *ftt, doc_id_t doc_id)
    {
            /* 1. Datensätze im fts_doc_id_index-Index basierend auf docid durchsuchen*/
          /* btr_cur_search_to_nth_level, btr_cur_search_to_nth_level wird in der Funktion btr_pcur_open_with_no_init aufgerufen
            Der B+-Baum-Suchvorgang wird durchgeführt. Zuerst wird der Blattknoten, in dem sich der Dokumentdatensatz befindet, vom Wurzelknoten aus gefunden, und dann wird der Dokumentdatensatz durch eine binäre Suche gefunden. */
        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) { /* Wenn ein Dokumentdatensatz gefunden wird*/
            if (is_id_cluster) {
                 /** 1.1 Wenn fts_doc_id_index ein Clustered-Index ist, bedeutet dies, dass die Zeilendatensatzdaten gefunden wurden und der Zeilendatensatz direkt gespeichert wird**/
                doc_pcur = &pcur;
              } anders {
                /** 1.2 Wenn fts_doc_id_index ein sekundärer Index ist, müssen Sie den Zeilendatensatz im Clustered-Index basierend auf der in 1.1 gefundenen Primärschlüssel-ID weiter durchsuchen und den Zeilendatensatz speichern, nachdem Sie ihn gefunden haben **/ btr_pcur_open_with_no_init(clust_index, clust_ref, PAGE_CUR_LE,
                                           BTR_SEARCH_LEAF, &clust_pcur, 0, &mtr);
               doc_pcur = &clust_pcur;
             } // Cache durchqueren->get_docs
            for (ulint i = 0; i < num_idx; ++i) {
                /***** 2. Führen Sie eine Wortsegmentierung für das Dokument durch, erhalten Sie das zugehörige Paar {Wort (das Dokument, in dem sich das Wort befindet, der Versatz des Wortes im Dokument)} und fügen Sie es dem Cache hinzu** ***/
                fts_doc_t doc;
                fts_doc_init(&doc);
        /** 2.1 Rufen Sie das Inhaltsdokument der entsprechenden Spalte des Volltextindex im Zeilendatensatz gemäß der doc_id ab und analysieren Sie das Dokument, hauptsächlich um die Token der fts_doc_t-Struktur zu erstellen. Die Token sind ein rot-schwarzer Baum Jedes Element ist ein {Wort, [die Struktur der Position, an der das Wort im Dokument erscheint]}, und die Analyseergebnisse werden im Dokument gespeichert**/
                fts_fetch_doc_from_rec(ftt->fts_trx->trx, get_doc, clust_index,doc_pcur, offsets, &doc);
                /** 2.2 Fügen Sie das in Schritt 2.1 erhaltene {Wort, [die Position, an der das Wort im Dokument erscheint]} zu index_cache hinzu**/
                fts_cache_add_doc(table->fts->cache, get_doc->index_cache, doc_id, doc.tokens);
               /***** 3. Bestimmen Sie, ob Cache->total_size den Schwellenwert erreicht. Wenn der Schwellenwert erreicht ist, fügen Sie eine FTS_MSG_SYNC_TABLE-Nachricht zur Nachrichtenwarteschlange des schmutzigen Threads hinzu, um den Thread zum Bereinigen zu benachrichtigen *****/
                bool need_sync = false;
                if ((cache->total_size - Cache->total_size_before_sync >
                     fts_max_cache_size / 10 || fts_need_sync) &&!cache->sync->in_progress) {
                  /** 3.1 Bestimmen Sie, ob der Schwellenwert erreicht ist**/
                  need_sync = true;
                  Cache->total_size_before_sync = Cache->total_size;
                }
                    if (need_sync) {
                    /** 3.2 Packen Sie die FTS_MSG_SYNC_TABLE-Nachricht, stellen Sie sie in die fts_optimize_wq-Warteschlange und benachrichtigen Sie den fts_optimize_thread-Thread, um sie zu bereinigen. Der Inhalt der Nachricht ist Tabellen-ID **/ fts_optimize_request_sync_table(table);
                }
            }
        }
    }

Nachdem Sie den obigen Prozess verstanden haben, können Sie das auf der offiziellen Website beschriebene spezielle Phänomen der Volltextindex-Transaktionsverarbeitung erklären. Wenn Sie einige Zeilendatensätze einfügen Wenn die aktuelle Transaktion in der Volltextindextabelle nicht festgeschrieben ist, können wir den eingefügten Zeilendatensatz nicht über den Volltextindex in der aktuellen Transaktion finden. Der Grund dafür ist, dass die Aktualisierung des Volltextindex abgeschlossen ist, wenn die Transaktion nicht festgeschrieben wurde. Daher kann der Datensatz nicht über den Volltextindex gefunden werden. Aus Abschnitt 3.1 können wir jedoch erkennen, dass der Innodb-Zeilendatensatz zu diesem Zeitpunkt eingefügt wurde. Wenn Sie den Volltextindex abfragen, können Sie den Datensatz finden, indem Sie SELECT COUNT(*) FROM Opening_lines direkt ausführen.

3.3 Bürstenphase

Der Hauptablauf der Reinigungsphase ist in der folgenden Abbildung dargestellt:

4.png

Wenn InnoDB startet, wird ein Hintergrundthread erstellt, die Thread-Funktion ist fts_optimize_thread und die Arbeitswarteschlange ist fts_optimize_wq. Wenn in der Transaktionsübermittlungsphase von Abschnitt 3.2 der Cache voll ist, fügt die Funktion fts_optimize_request_sync_table eine FTS_MSG_SYNC_TABLE-Nachricht zur fts_optimize_wq-Warteschlange hinzu. Der Hintergrundthread entfernt die Nachricht und leert den Cache auf die Festplatte. Der Funktionsaufrufstapel lautet wie folgt:

 

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

Die von diesem Thread ausgeführten Hauptoperationen sind wie folgt:

  1. Holen Sie sich eine Nachricht aus der fts_optimize_wq-Warteschlange.
  2. Bestimmen Sie den Nachrichtentyp. Wenn es sich um FTS_MSG_SYNC_TABLE handelt, führen Sie eine Löschung durch.
  3. Leeren Sie den Inhalt des Caches in die Hilfstabelle auf der Festplatte.
  4. Leeren Sie den Cache und versetzen Sie ihn in seinen Ausgangszustand.
  5. Kehren Sie zu Schritt 1 zurück und erhalten Sie die nächste Nachricht.

Wenn in Abschnitt 3.2 die Gesamtgröße des FTS-Cache größer als der festgelegte Speichergrößenschwellenwert ist, wird eine FTS_MSG_SYNC_TABLE geschrieben und in die fts_optimize_wq-Warteschlange eingefügt. Der Dirty-Thread verarbeitet die Nachricht und spült die Daten hinein Kopieren Sie den FTS-Cache auf die Festplatte und leeren Sie dann den Cache.

Es ist erwähnenswert, dass, wenn die Gesamtgröße des FTS-Cache größer als der festgelegte Schwellenwert für die Speichergröße ist, nur eine Nachricht in die Warteschlange fts_optimize_wq geschrieben wird. Zu diesem Zeitpunkt kann der FTS-Cache noch Daten und Speicher schreiben, bevor er verarbeitet wird Der Hintergrund-Flush-Thread wird weiter zunehmen, was auch die Hauptursache für das OOM-Problem ist, das das gleichzeitige Einfügen von Volltextindizes verursacht. Die Lösung für das Problem ist Patch Bug #32831765 SERVER HITS OOM CONDITION WHEN LOAD TWO INNODB kann es selbst überprüfen. 

OOM-Check-Link: https://bugs.mysql.com/bug.php?id=103523

Wenn der Dirty-Thread den FTS-Cache einer bestimmten Tabelle noch nicht verschmutzt hat, stürzt der MySQL-Prozess ab und die Daten im Cache gehen verloren. Nach dem Neustart werden bei der ersten Ausführung von insert oder select in der Tabelle die Daten im Cache vor dem Absturz in der Funktion fts_init_index wiederhergestellt. Zu diesem Zeitpunkt wird die auf der Festplatte abgelegte synced_doc_id aus der Konfiguration gelesen Tabelle, und die synced_doc_id in der Tabelle ist größer als synced_doc_id. Die Datensätze werden gelesen und wortsegmentiert und im Cache wiederhergestellt. Informationen zur spezifischen Implementierung finden Sie in den Funktionen fts_doc_fetch_by_doc_id und fts_init_recover_doc.

Klicken Sie hier, um zu folgen und so schnell wie möglich mehr über die neuen Technologien von Huawei Cloud zu erfahren~

 

Die Raubkopien von „Celebrating More Than Years 2“ wurden auf npm hochgeladen, was dazu führte, dass npmmirror den Unpkg-Dienst einstellen musste und sich gemeinsam mit Hunderten von Menschen in die USA begab Front-End-Visualisierungsbibliothek und Baidus bekanntes Open-Source-Projekt ECharts – „Going to the Sea“ zur Unterstützung Fischbetrüger nutzten TeamViewer, um 3,98 Millionen zu überweisen! Was sollten Remote-Desktop-Anbieter tun? Zhou Hongyi: Für Google bleibt nicht mehr viel Zeit. Es wird empfohlen, dass alle Produkte Open Source sind. Ein ehemaliger Mitarbeiter eines bekannten Open-Source-Unternehmens brachte die Nachricht: Nachdem er von seinen Untergebenen herausgefordert wurde, wurde der technische Leiter wütend hat die schwangere Mitarbeiterin entlassen. Google hat gezeigt, wie man ChromeOS in einer virtuellen Android-Maschine ausführt. Bitte geben Sie mir einen Rat, welche Rolle time.sleep(6) hier spielt. Microsoft reagiert auf Gerüchte, dass Chinas KI-Team „für die USA packt“. People's Daily Online kommentiert die matroschkaartige Aufladung von Bürosoftware: Nur durch das aktive Lösen von „Sets“ können wir eine Zukunft haben
{{o.name}}
{{m.name}}

Ich denke du magst

Origin my.oschina.net/u/4526289/blog/11179935
Empfohlen
Rangfolge