Überblick
Nach Jahren der Entwicklung hat sich MySQL zur beliebtesten Datenbank entwickelt, die in der Internetbranche weit verbreitet ist und nach und nach in verschiedene traditionelle Branchen vordringt. Der Grund für seine Beliebtheit liegt einerseits in seinen hervorragenden Fähigkeiten zur Verarbeitung von Transaktionen mit hoher Parallelität, andererseits profitiert es auch vom reichhaltigen Ökosystem von MySQL. MySQL funktioniert gut bei der Verarbeitung kurzer Abfragen in OLTP-Szenarien, seine Fähigkeit, komplexe große Abfragen zu verarbeiten, ist jedoch begrenzt. Der direkteste Punkt ist, dass MySQL für eine SQL-Anweisung höchstens einen CPU-Kern zur Verarbeitung verwenden kann. In diesem Szenario können die Multi-Core-Funktionen der Host-CPU nicht genutzt werden. MySQL steht nicht still und entwickelt sich weiter. Die neu eingeführte Version 8.0.14 führt erstmals die Funktion für parallele Abfragen ein, die die Leistung von Anweisungen vom Typ „check table“ und „select count(*)“ verdoppelt. Obwohl die aktuellen Nutzungsszenarien noch relativ begrenzt sind, lohnt es sich, auf die weitere Entwicklung zu blicken.
Empfehlung: "MySQL-Video-Tutorial"
Verwendung
Legen Sie die Anzahl gleichzeitiger Threads fest, indem Sie den Parameter konfigurieren innodb_parallel_read_threads. Kann die parallele Scanfunktion starten, der Standardwert ist 4. Ich werde hier ein einfaches Experiment durchführen, 200 Millionen Daten über Sysbench importieren und innodb_parallel_read_threads
auf 1, 2, 4, 8, 16, 32 bzw. 64 konfigurieren, um die Auswirkung der parallelen Ausführung zu testen. Die Testanweisung ist select count(*) from sbtest1;
Die horizontale Achse ist die Anzahl der konfigurierten gleichzeitigen Threads und die vertikale Achse ist die Ausführungszeit der Anweisung. Den Testergebnissen zufolge ist die Parallelleistung insgesamt immer noch gut. Das Scannen von 200 Millionen Datensätzen sank von 18 Sekunden für einen einzelnen Thread auf 1 Sekunde für 32 Threads. Unabhängig davon, wie viel Parallelität in Zukunft entwickelt wird, übersteigt der Verwaltungsverbrauch von Multithreads aufgrund der begrenzten Datenmenge die durch Parallelität erzielte Leistungsverbesserung, und die SQL-Ausführungszeit kann nicht weiter verkürzt werden.
Parallele Ausführung von MySQL
Tatsächlich befindet sich die aktuelle parallele Ausführung von MySQL noch in einem sehr frühen Stadium, wie in der Abbildung unten dargestellt Die vorherige MySQL-Serienverarbeitung eines einzelnen SQL-Formulars ist die parallele Fähigkeit, die von der aktuellen MySQL-Version bereitgestellt wird, und die ganz rechte Form ist die Form, die MySQL in Zukunft entwickeln wird Der Optimierer generiert einen Parallelplan basierend auf der Systemlast und SQL und sendet den Partitionsplan zur Parallelisierungsimplementierung an den Executor. Bei der parallelen Ausführung handelt es sich nicht nur um paralleles Scannen, sondern auch um parallele Aggregation, parallele Verknüpfung, parallele Gruppierung und parallele Sortierung. Es gibt keine unterstützenden Änderungen am übergeordneten Optimierer und Executor der aktuellen Version von MySQL. Daher konzentriert sich die folgende Diskussion hauptsächlich darauf, wie die InnoDB-Engine das parallele Scannen implementiert, hauptsächlich einschließlich Partitionierung, paralleles Scannen, Vorauslesen und Adapterklassen, die mit dem Executor interagieren.
Partitionierung
Ein zentraler Schritt des parallelen Scannens ist die Partitionierung, die die gescannten Daten in mehrere Teile aufteilt, sodass mehrere Threads dies tun können Paralleler Scan. Die InnoDB-Engine ist eine indexorganisierte Tabelle. Die Einheit eines Knotens ist eine Seite (Block/Seite). Gleichzeitig werden Hot-Pages im Pufferpool gespeichert und durch den LRU-Algorithmus eliminiert. Die Logik der Partitionierung besteht darin, von der Wurzelknotenseite aus zu beginnen und Schicht für Schicht nach unten zu scannen. Wenn festgestellt wird, dass die Anzahl der Zweige auf einer bestimmten Ebene die konfigurierte Anzahl von Threads überschreitet, wird die Aufteilung beendet. Während der Implementierung werden tatsächlich insgesamt zwei Partitionen durchgeführt. Die erste Partition basiert auf der Anzahl der Zweige auf der Wurzelknotenseite. Der Datensatz des äußersten linken Blattknotens jedes Zweigs ist die linke Untergrenze, und dieser Datensatz wird aufgezeichnet als angrenzende Obergrenze. Die obere rechte Grenze eines Zweigs. Auf diese Weise wird der B+-Baum in mehrere Teilbäume unterteilt, und jeder Teilbaum ist eine Scanpartition. Nach der ersten Partition kann es zu einem Problem kommen, dass die Anzahl der Partitionen den Multi-Core nicht vollständig ausnutzen kann. Wenn der parallele Scan-Thread beispielsweise als 3 konfiguriert ist und nach der ersten Partition 4 Partitionen generiert werden, dann nach Die ersten drei Partitionen werden parallel fertiggestellt, die vierte. Jede Partition kann höchstens von einem Thread gescannt werden, und der Endeffekt ist, dass Multi-Core-Ressourcen nicht vollständig genutzt werden können.
Sekundäre Partitionierung
Um dieses Problem zu lösen, führt Version 8.0.17 die sekundäre Partitionierung ein. Erkunden Sie für die vierte Partition weiterhin die Aufteilung, so viele Sub-. Partitionen können gleichzeitig gescannt werden, und die minimale Granularität des gleichzeitigen Scannens durch die InnoDB-Engine ist die Seitenebene. Die spezifische Logik zur Beurteilung der sekundären Partitionierung besteht darin, dass nach der primären Partitionierung, wenn die Anzahl der Partitionen größer als die Anzahl der Threads ist, die Partitionen, deren Anzahl größer als die Anzahl der Threads ist, für die sekundäre Partitionierung fortgesetzt werden müssen kleiner als die Anzahl der Threads ist und die B+-Baumebene sehr tief ist, dann ist für alle Partitionen eine sekundäre Partitionierung erforderlich.
Der relevante Code lautet wie folgt:
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,数据量很大),则所有分区都需要进行二次分区 }
Unabhängig davon, ob es sich um eine primäre Partition oder eine sekundäre Partition handelt, ist die Logik der Partitionsgrenzen dieselbe. Der Datensatz des äußersten linken Blattknotens jeder Partition ist die untere linke Grenze und zeichnet diesen Datensatz als obere rechte Grenze des angrenzenden vorherigen Zweigs auf. Dadurch wird sichergestellt, dass genügend Partitionen, eine ausreichende Granularität und ausreichende Parallelität vorhanden sind. Die folgende Abbildung zeigt die Konfiguration von drei gleichzeitigen Threads, die nach sekundärer Partitionierung suchen.
Der relevante Code lautet wie folgt:
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)
Paralleler Scan
Stellen Sie nach einer Partition jede Partitions-Scan-Aufgabe in eine sperrenfreie Warteschlange und führen Sie die Scan-Aufgabe zu diesem Zeitpunkt aus Die Aufgabe wird zweimal aufgeteilt und in die Warteschlange gestellt. Dieser Prozess umfasst hauptsächlich zwei Kernschnittstellen, eine ist die Worker-Thread-Schnittstelle und die andere ist die Traversal-Record-Schnittstelle. Erstere ruft Aufgaben aus der Warteschlange ab und führt sie aus, und letztere erhält entsprechende Datensätze basierend auf Sichtbarkeit und Injektionen Sie verarbeiten sie über die Rückruffunktion der oberen Ebene, z. B. Zählen usw.
Parallel_reader::worker(size_t thread_id)
{
1. Extrahieren Sie die CTX-Aufgabe aus der CTX-Warteschlange
2. Gemäß dem Split-Attribut von ctx, Bestimmen Sie, ob die Partition weiter aufgeteilt werden muss (split())
3. Durchlaufen Sie alle Datensätze in der Partition (traverse())
4. Nachdem eine Partitionsaufgabe abgeschlossen ist , behalten Sie den m_n_completed-Zähler bei
5. Wenn der m_n_compeleted-Zähler die ctx-Nummer erreicht, aktivieren Sie alle Arbeitsthreads und beenden Sie
6. Geben Sie Fehlerinformationen gemäß der Traverse-Schnittstelle zurück.
}
Parallel_reader::Ctx::traverse()
{
1. Pcursor entsprechend dem Bereich einstellen
2. Finden Sie btree, positionieren Sie den Cursor an der Startposition des Bereichs
3. Bestimmen Sie die Sichtbarkeit (check_visibility)
4. Wenn sichtbar, berechnen Sie gemäß der Rückruffunktion (z. B. Statistiken)
5. Wenn der letzte Datensatz der Seite erreicht ist, starten Sie den Read-Ahead-Mechanismus (submit_read_ahead)
6. Beenden Sie nach Überschreiten des Bereichs
}
Gleichzeitig führt Version .17 in Version 8.0 auch einen Read-Ahead-Mechanismus ein, um das Problem einer schlechten Parallelleistung aufgrund von E/A-Engpässen zu vermeiden. Derzeit kann die Anzahl der Threads für das Vorlesen nicht konfiguriert werden und ist im Code fest auf 2 Threads codiert. Die Einheit jedes Vorlesevorgangs ist ein Cluster (InnoDB-Dateien werden über eine dreistufige Struktur aus Segmenten, Clustern und Seiten verwaltet. Ein Cluster ist eine Gruppe aufeinanderfolgender Seiten), der je nach Größe 1 MB oder 2 MB groß sein kann die Seitenkonfiguration. Bei einer üblichen 16-KByte-Seitenkonfiguration wird jedes Mal 1 MB vorgelesen, was 64 Seiten entspricht. Wenn der Arbeitsthread scannt, ermittelt er zunächst, ob die nächste angrenzende Seite die erste Seite des Clusters ist. Wenn dies der Fall ist, wird eine Vorleseaufgabe gestartet. Read-Ahead-Aufgaben werden ebenfalls über die sperrenfreie Warteschlange zwischengespeichert. Der Worker-Thread ist der Produzent und der Read-Ahead-Worker ist der Consumer. Da sich nicht alle Partitionsseiten überlappen, werden Vorausleseaufgaben nicht wiederholt.
Executor-Interaktion (Adapter)
Tatsächlich hat MySQL eine Adapterklasse Parallel_reader_adapter für die Verwendung in der oberen Schicht gekapselt, um sich auf die nachfolgende umfangreichere parallele Ausführung vorzubereiten. Zunächst muss diese Klasse das Problem des Datensatzformats lösen und die von der Engine-Schicht gescannten Datensätze in das MySQL-Format konvertieren. Auf diese Weise werden die obere und untere Ebene entkoppelt. Der Executor muss sich der Engine nicht bewusst sein Layer-Format und wird im MySQL-Format verarbeitet. Der gesamte Prozess ist ein Fließband. Der Arbeitsthread liest die Datensätze kontinuierlich von der oberen Ebene Die Geschwindigkeit kann durch den Puffer ausgeglichen werden. Stellen Sie sicher, dass der gesamte Prozess abläuft. Die Standard-Cache-Größe beträgt 2 MB. Die Anzahl der MySQL-Datensätze, die der Puffer zwischenspeichern kann, wird basierend auf der Datensatzzeilenlänge der Tabelle bestimmt. Der Kernprozess befindet sich hauptsächlich in der Schnittstelle „process_rows“. Der Prozess ist wie folgt:
process_rows
{
1. Konvertieren Sie Engine-Datensätze in MySQL-Datensätze
2 . Holen Sie sich diese Thread-Pufferinformationen (wie viele MySQL-Datensätze wurden konvertiert und wie viele wurden an die obere Ebene gesendet)
3. Füllen Sie die MySQL-Datensätze in den Puffer und erhöhen Sie die Statistik m_n_read
4 . Rufen Sie die Rückruffunktion zur Verarbeitung auf (z. B. Statistiken, Aggregation, Sortierung usw.), Statistiken automatisch inkrementieren Informationen zur Tabelle und Verarbeitung von Datensatzrückruffunktionen, z. B. Verarbeitungsaggregation, Sortierung und Gruppenarbeit. Die Rückruffunktion wird durch die Einstellung m_init_fn, m_load_fn und m_end_fn gesteuert.
ZusammenfassungMySQL8.0 hat die parallele Abfrage eingeführt, obwohl sie noch relativ rudimentär ist, hat sie uns bereits ermöglicht, das Potenzial der parallelen Abfrage von MySQL zu erkennen Aus dem Experiment geht hervor, dass nach der Aktivierung der parallelen Ausführung die Multi-Core-Funktionen bei der Ausführung von SQL-Anweisungen vollständig ausgenutzt werden und die Antwortzeit stark abnimmt. Ich glaube, dass 8.0 in naher Zukunft mehr parallele Operatoren unterstützen wird, einschließlich paralleler Aggregation, paralleler Verbindung, paralleler Gruppierung und paralleler Sortierung.
Das obige ist der detaillierte Inhalt vonDetaillierte Erläuterung der parallelen Ausführung von MySQL8.0 InnoDB. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!