Die folgende Demonstration basiert auf MySQL Version 5.7.27 1. Einführung in die MySQL-Unterabfrage-Optimierungsstrategie: Unterabfrage-Optimierungsstrategie Der Optimierer wählt für verschiedene Arten von Unterabfragen unterschiedliche Strategien. 1. Für IN- und =ANY-Unterabfragen stehen dem Optimierer die folgenden Strategieoptionen zur Verfügung:
2. Für NOT IN- und <> ALL-Unterabfragen stehen dem Optimierer folgende Strategieoptionen zur Verfügung:
3. Für abgeleitete Tabellen stehen dem Optimierer folgende Strategieoptionen zur Verfügung: 2. Daten zur Simulationsdemonstration erstellen Um die Analyse des Problems zu erleichtern, erstellen Sie zwei Tabellen und fügen Sie simulierte Daten ein: CREATE TABLE `test02` ( `id` int(11) NICHT NULL, `a` int(11) DEFAULT NULL, `b` int(11) DEFAULT NULL, Primärschlüssel (`id`), SCHLÜSSEL `a` (`a`) )ENGINE=InnoDB; Prozedur-IDA löschen; Trennzeichen;; Prozedur idata() erstellen beginnen deklariere i int; setze i=1; während(i<=10000)mache in test02 Werte (i, i, i) einfügen; setze i=i+1; Ende während; Ende;; Trennzeichen ; rufen Sie idata() auf; Erstellen Sie die Tabelle test01 wie test02; in test01 einfügen (select * from test02 where id<=1000) 3. Beispielanalyse von SQL-Instanzen Beispiel für eine Unterabfrage: Wählen Sie * aus test01, wobei test01.a in (Wählen Sie test02.b aus test02, wobei id < 10) Die meisten Leute würden einfach denken, dass dieses SQL wie folgt ausgeführt wird: Wählen Sie test02.b aus test02, wobei id < 10 ist. Ergebnisse: 1, 2, 3, 4, 5, 6, 7, 8, 9 Wählen Sie * aus Test01, wobei Test01.a in (1,2,3,4,5,6,7,8,9) ist. Aber so funktioniert MySQL eigentlich nicht. MySQL schiebt die zugehörige äußere Tabelle in die Unterabfrage und der Optimierer geht davon aus, dass dies effizienter ist. Mit anderen Worten: Der Optimierer schreibt das obige SQL wie folgt um: wähle * aus test01, wo existiert (wähle b aus test02, wo ID < 10 und test01.a=test02.b); Tipp: Für MySQL 5.5 und frühere Versionen Überprüfen Sie den Ausführungsplan wie folgt und stellen Sie fest, dass dieses SQL 1000 Mal einen vollständigen Tabellenscan für die Tabelle test01 durchführt, was ineffizient ist: root@localhost [dbtest01]>desc select * from test01, wo vorhanden (select b from test02, wo id < 10 und test01.a=test02.b); +----+--------------------+--------+------------+-------+---------------+---------+---------+---------+---------+---------+----------+----------+-------------+ | ID | Auswahltyp | Tabelle | Partitionen | Typ | mögliche Schlüssel | Schlüssel | Schlüssellänge | Ref. | Zeilen | gefiltert | Extra | +----+--------------------+--------+------------+-------+---------------+---------+---------+---------+---------+---------+----------+----------+-------------+ | 1 | PRIMARY | test01 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | Verwenden von „where“ | | 2 | ABHÄNGIGE UNTERABFRAGE | test02 | NULL | Bereich | PRIMÄR | PRIMÄR | 4 | NULL | 9 | 10.00 | Verwenden von „where“ | +----+--------------------+--------+------------+-------+---------------+---------+---------+---------+---------+---------+----------+----------+-------------+ 2 Zeilen im Satz, 2 Warnungen (0,00 Sek.) Aber wenn wir das folgende SQL tatsächlich ausführen, stellen wir fest, dass es überhaupt nicht langsam ist. Ist das nicht ein Widerspruch? Keine Sorge, lasst uns weiter analysieren: Wählen Sie * aus test01, wobei test01.a in (Wählen Sie test02.b aus test02, wobei id < 10) Überprüfen Sie den Ausführungsplan dieses SQL wie folgt: root@localhost [dbtest01]>desc SELECT * FROM test01 WHERE test01.a IN (SELECT test02.b FROM test02 WHERE id < 10); +----+--------------+----------+-------------+-----------+---------------+--------+---------+------------+---------+-----------+----------+----------+-------------+ | ID | Auswahltyp | Tabelle | Partitionen | Typ | mögliche Schlüssel | Schlüssel | Schlüssellänge | Ref. | Zeilen | gefiltert | Extra | +----+--------------+----------+-------------+-----------+---------------+--------+---------+------------+---------+-----------+----------+----------+-------------+ | 1 | SIMPLE | <subquery2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | 100,00 | Verwenden von „where“ | | 1 | EINFACH | test01 | NULL | ref | a | a | 5 | <Unterabfrage2>.b | 1 | 100,00 | NULL | | 2 | MATERIALISIERT | test02 | NULL | Bereich | PRIMÄR | PRIMÄR | 4 | NULL | 9 | 100,00 | Verwenden von „where“ | +----+--------------+----------+-------------+-----------+---------------+--------+---------+------------+---------+-----------+----------+----------+-------------+ 3 Zeilen im Satz, 1 Warnung (0,00 Sek.) Es wurde festgestellt, dass der Optimierer die MATERIALIZED-Strategie verwendet. Also habe ich nach Informationen gesucht und diese Strategie studiert. Der Grund dafür ist, dass der Optimierer seit MySQL 5.6, einschließlich MySQL 5.6, neue Optimierungsstrategien eingeführt hat: Materialisierung=[aus|ein], Semijoin=[aus|ein], (aus bedeutet, diese Strategie auszuschalten, ein bedeutet, diese Strategie einzuschalten). Die Standard-Optimierungsstrategie für MySQL 5.7.27 ist: root@localhost [dbtest01]>Variablen wie „optimizer_switch“ anzeigen; +------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Variablenname | Wert | +------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | optimizer_switch | index_merge=ein,index_merge_union=ein,index_merge_sort_union=ein,index_merge_intersection=ein,engine_condition_pushdown=ein,index_condition_pushdown=ein,mrr=ein,mrr_cost_based=ein,block_nested_loop=ein,batched_key_access=aus,materialization=ein,semijoin=ein,loosescan=ein,firstmatch=ein,duplicateweedout=ein,subquery_materialization_cost_based=ein,use_index_extensions=ein,condition_fanout_filter=ein,derived_merge=ein | +------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ In MySQL 5.6 und höher Die Ausführung des folgenden SQL wird nicht langsam sein. Da die MySQL-Optimierungsstrategien Materialisierung und Semijoin diese SQL optimieren Wählen Sie * aus test01, wobei test01.a in (Wählen Sie test02.b aus test02, wobei id < 10) Wir haben jedoch zum Testen die MySQL-Optimierungsstrategien Materialisierung und Semijoin deaktiviert und festgestellt, dass SQL die gesamte Tabelle von test01 (1000) gescannt hat: Setzen Sie den globalen Optimiererschalter auf „Materialisierung=aus, Semijoin=aus“. Der Ausführungsplan sieht wie folgt aus. Die Tabelle test01 wird tatsächlich vollständig gescannt: root@localhost [dbtest01]>desc SELECT * FROM test01 WHERE test01.a IN (SELECT test02.b FROM test02 WHERE id < 10); +----+--------------------+--------+------------+-------+---------------+---------+---------+---------+---------+---------+----------+----------+-------------+ | ID | Auswahltyp | Tabelle | Partitionen | Typ | mögliche Schlüssel | Schlüssel | Schlüssellänge | Ref. | Zeilen | gefiltert | Extra | +----+--------------------+--------+------------+-------+---------------+---------+---------+---------+---------+---------+----------+----------+-------------+ | 1 | PRIMARY | test01 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | Verwenden von „where“ | | 2 | ABHÄNGIGE UNTERABFRAGE | test02 | NULL | Bereich | PRIMÄR | PRIMÄR | 4 | NULL | 9 | 10.00 | Verwenden von „where“ | +----+--------------------+--------+------------+-------+---------------+---------+---------+---------+---------+---------+----------+----------+-------------+ 2 Zeilen im Satz, 1 Warnung (0,00 Sek.) Lassen Sie uns diesen Ausführungsplan analysieren: ! ! ! ! Noch ein Tipp: Wenn Sie MySQL 5.5 oder frühere Versionen oder MySQL 5.6 oder spätere Versionen verwenden und die Optimierungsstrategie materialization=off, semijoin=off deaktivieren, erhalten Sie den folgenden SQL-Ausführungsplan. root@localhost [dbtest01]>desc select * from test01, wo vorhanden (select b from test02, wo id < 10 und test01.a=test02.b); +----+--------------------+--------+------------+-------+---------------+---------+---------+---------+------+---------+----------+----------+-------------+ | ID | Auswahltyp | Tabelle | Partitionen | Typ | mögliche Schlüssel | Schlüssel | Schlüssellänge | Ref. | Zeilen | gefiltert | Extra | +----+--------------------+--------+------------+-------+---------------+---------+---------+---------+------+---------+----------+----------+-------------+ | 1 | PRIMARY | test01 | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 100.00 | Verwenden von „where“ | | 2 | ABHÄNGIGE UNTERABFRAGE | test02 | NULL | Bereich | PRIMÄR | PRIMÄR | 4 | NULL | 9 | 10.00 | Verwenden von „where“ | +----+--------------------+--------+------------+-------+---------------+---------+---------+---------+------+---------+----------+----------+-------------+ 2 Zeilen im Satz, 2 Warnungen (0,00 Sek.) Die nicht korrelierte Unterabfrage wird zu einer korrelierten Unterabfrage (select_type:DEPENDENT SUBQUERY). Die Unterabfrage muss mit der äußeren Tabelle test01 basierend auf b verknüpft werden. Da das Feld test01 der äußeren Tabelle erforderlich ist, kann die Unterabfrage nicht zuerst ausgeführt werden. Der Ausführungsprozess ist:
Die Gesamtzahl der gescannten Zeilen beträgt 1000+1000*9=10000 (das ist ein theoretischer Wert, aber der tatsächliche Wert ist kleiner als 10000. Ich habe nie herausgefunden, wie das zustande kam. Die Regel ist, dass für jede zusätzliche Zeile im Ergebnissatz der Unterabfrage die Gesamtzahl der gescannten Zeilen um einige Zeilen reduziert wird). Semi-Join-Optimierer: Dies führt zu einem Problem. Wenn die äußere Tabelle sehr groß ist, muss die Unterabfrage einmal für jede Zeile der äußeren Abfrage ausgeführt werden, und die Leistung dieser Abfrage wird sehr schlecht sein. Zur Verbesserung der Effizienz kann man leicht daran denken, es in „Join“ umzuschreiben: wähle test01.* aus test01, führe test02 aus mit test01.a=test02.b und test02.id<10; # Zeigen Sie den Ausführungsplan dieses SQL an: desc wähle test01.* aus test01, schließe dich test02 an auf test01.a=test02.b und test02.id<10; root@localhost [dbtest01]>EXPLAIN erweiterte Auswahl test01.* von test01 verbinden test02 auf test01.a=test02.b und test02.id<10; +----+----------+--------+------------+-------+---------------+--------+---------+-------------------+----------+----------+-------------+ | ID | Auswahltyp | Tabelle | Partitionen | Typ | mögliche Schlüssel | Schlüssel | Schlüssellänge | Ref. | Zeilen | gefiltert | Extra | +----+----------+--------+------------+-------+---------------+--------+---------+-------------------+----------+----------+-------------+ | 1 | SIMPLE | test02 | NULL | Bereich | PRIMARY | PRIMARY | 4 | NULL | 9 | 100,00 | Verwenden von „where“ | | 1 | EINFACH | test01 | NULL | ref | a | a | 5 | dbtest01.test02.b | 1 | 100,00 | NULL | +----+----------+--------+------------+-------+---------------+--------+---------+-------------------+----------+----------+-------------+ 2 Zeilen im Satz, 2 Warnungen (0,00 Sek.) Durch diese Optimierung kann die Tabelle t2 als treibende Tabelle fungieren und die zugehörigen Felder der Tabelle t1 verfügen über Indizes, wodurch die Suchleistung sehr hoch wird. Hier gibt es jedoch ein Problem. Join kann zu doppelten Ergebnissen führen, während die Unterabfragesemantik (select ...) keine doppelten Werte liefert. Der SQL- und Ausführungsplan nach der Semijoin-Optimierung lautet wie folgt: root@localhost [dbtest01]>desc SELECT * FROM test01 WHERE test01.a IN (SELECT test02.b FROM test02 WHERE id < 10); +----+--------------+----------+-------------+-----------+---------------+--------+---------+------------+---------+-----------+----------+----------+-------------+ | ID | Auswahltyp | Tabelle | Partitionen | Typ | mögliche Schlüssel | Schlüssel | Schlüssellänge | Ref. | Zeilen | gefiltert | Extra | +----+--------------+----------+-------------+-----------+---------------+--------+---------+------------+---------+-----------+----------+----------+-------------+ | 1 | SIMPLE | <subquery2> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | 100,00 | Verwenden von „where“ | | 1 | EINFACH | test01 | NULL | ref | a | a | 5 | <Unterabfrage2>.b | 1 | 100,00 | NULL | | 2 | MATERIALISIERT | test02 | NULL | Bereich | PRIMÄR | PRIMÄR | 4 | NULL | 9 | 100,00 | Verwenden von „where“ | +----+--------------+----------+-------------+-----------+---------------+--------+---------+------------+---------+-----------+----------+----------+-------------+ 3 Zeilen im Satz, 1 Warnung (0,00 Sek.) wählen `test01`.`id`,`test01`.`a`,`test01`.`b` von `test01` Semi-Join `test02` Wo ((`test01`.`a` = `<Unterabfrage2>`.`b`) und (`test02`.`id` < 10)); ##Beachten Sie, dass dies das vom Optimierer neu geschriebene SQL ist. Die Semi-Join-Syntax kann auf dem Client nicht verwendet werden. Die Optimierungsimplementierung von Semijoin ist relativ komplex und wird in Strategien wie FirstMatch und Materialize unterteilt. Im obigen Ausführungsplan bedeutet select_type=MATERIALIZED, dass Semijoin mit der Materialize-Strategie implementiert wird. Führen Sie zunächst die Unterabfrage aus und speichern Sie das Ergebnis in einer temporären Tabelle. Diese temporäre Tabelle verfügt über einen Primärschlüssel zur Deduplizierung. Eine weitere in MySQL 5.6 hinzugefügte Optimierungsfunktion ist die Materialisierung, die die Ergebnisse der Unterabfrage in einer temporären Tabelle materialisiert und sie dann in die äußere Suchabfrage einsetzt, um die Abfrageausführung zu beschleunigen. Die temporäre Tabelle im Arbeitsspeicher enthält den Primärschlüssel (Hash-Index), eliminiert doppelte Zeilen und verkleinert die Tabelle. Das Öffnen von Semijoin und Materialisierung wird durch die Flags semijoin={on|off} und materialization={on|off} im Parameter optimizer_switch gesteuert. Hier ist ein Beispiel für eine löschbezogene Unterabfrage: Füllen Sie die beiden obigen Testtabellen mit 3,5 Millionen Daten bzw. 500.000 Daten, um die Löschanweisung zu testen root@localhost [dbtest01]>Wählen Sie count(*) aus test02 aus. +----------+ | Anzahl(*) | +----------+ |3532986| +----------+ 1 Zeile im Satz (0,64 Sek.) root@localhost [dbtest01]>Tabelle test01 wie test02 erstellen; Abfrage OK, 0 Zeilen betroffen (0,01 Sek.) root@localhost [dbtest01]>insert in test01 (wählen Sie * aus test02, wo id<=500000) root@localhost [dbtest01]>Wählen Sie count(*) aus test01; +----------+ | Anzahl(*) | +----------+ | 500000 | Die Ausführung der Löschanweisung dauerte 4 Sekunden. root@localhost [dbtest01]>löschen FROM test01 WHERE test01.a IN (SELECT test02.b FROM test02 WHERE id < 10); Abfrage OK, 9 Zeilen betroffen (4,86 Sek.) Wenn wir uns den Ausführungsplan ansehen, können wir sehen, dass die Tabelle test01 fast vollständig gescannt ist: root@localhost [dbtest01]>desc löschen FROM test01 WHERE test01.a IN (SELECT test02.b FROM test02 WHERE id < 10); +----+--------------------+--------+------------+-------+---------------+---------+---------+---------+---------+---------+----------+----------+-------------+ | ID | Auswahltyp | Tabelle | Partitionen | Typ | mögliche Schlüssel | Schlüssel | Schlüssellänge | Ref. | Zeilen | gefiltert | Extra | +----+--------------------+--------+------------+-------+---------------+---------+---------+---------+---------+---------+----------+----------+-------------+ | 1 | LÖSCHEN | test01 | NULL | ALLE | NULL | NULL | NULL | NULL | 499343 | 100,00 | Verwenden von „where“ | | 2 | ABHÄNGIGE UNTERABFRAGE | test02 | NULL | Bereich | PRIMÄR | PRIMÄR | 4 | NULL | 9 | 10.00 | Verwenden von „where“ | +----+--------------------+--------+------------+-------+---------------+---------+---------+---------+---------+---------+----------+----------+-------------+ 2 Zeilen im Satz (0,00 Sek.) Ändern Sie daher die obige SQL-Anweisung zum Löschen in eine Pseudo-Join-Anweisung. root@localhost [dbtest01]>desc lösche test01.* von test01, führe test02 mit test01.a=test02.b und test02.id<10 durch; +----+----------+--------+------------+-------+---------------+--------+---------+-------------------+----------+----------+-------------+ | ID | Auswahltyp | Tabelle | Partitionen | Typ | mögliche Schlüssel | Schlüssel | Schlüssellänge | Ref. | Zeilen | gefiltert | Extra | +----+----------+--------+------------+-------+---------------+--------+---------+-------------------+----------+----------+-------------+ | 1 | SIMPLE | test02 | NULL | Bereich | PRIMARY | PRIMARY | 4 | NULL | 9 | 100,00 | Verwenden von „where“ | | 1 | LÖSCHEN | test01 | NULL | ref | a | a | 5 | dbtest01.test02.b | 1 | 100.00 | NULL | +----+----------+--------+------------+-------+---------------+--------+---------+-------------------+----------+----------+-------------+ 2 Zeilen im Satz (0,01 Sek.) Die Ausführung ist sehr schnell. root@localhost [dbtest01]>delete test01.* from test01 join test02 on test01.a=test02.b and test02.id<10; Abfrage OK, 9 Zeilen betroffen (0,01 Sek.) root@localhost [dbtest01]>Wählen Sie test01.* aus test01 aus und verbinden Sie test02 mit test01.a=test02.b und test02.id<10; Leerer Satz (0,00 Sek.) Die folgende Tabellenausführung erfordert einen vollständigen Tabellenscan, der sehr langsam ist. Im Wesentlichen wird ein vollständiger Tabellenscan für die Tabelle test01 durchgeführt: root@lcalhost [dbtest01]>desc löschen FROM test01 WHERE id IN (SELECT id FROM test02 WHERE id='350000'); +----+--------------------+--------+------------+-------+---------------+---------+---------+---------+-----------+--------+-----------+-------------+ | ID | Auswahltyp | Tabelle | Partitionen | Typ | mögliche Schlüssel | Schlüssel | Schlüssellänge | Ref. | Zeilen | gefiltert | Extra | +----+--------------------+--------+------------+-------+---------------+---------+---------+---------+-----------+--------+-----------+-------------+ | 1 | LÖSCHEN | test01 | NULL | ALLE | NULL | NULL | NULL | NULL | 499343 | 100,00 | Verwenden von „where“ | | 2 | ABHÄNGIGE UNTERABFRAGE | test02 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | Index wird verwendet | +----+--------------------+--------+------------+-------+---------------+---------+---------+---------+-----------+--------+-----------+-------------+ 2 Zeilen im Satz (0,00 Sek.) Wenn jedoch Join verwendet wird, ist die Effizienz sehr hoch: root@localhost [dbtest01]>desc lösche test01.* FROM test01 inner join test02 WHERE test01.id=test02.id und test02.id=350000; +----+----------+--------+------------+-------+---------------+--------+---------+---------+----------+-------+----------+-------------+ | ID | Auswahltyp | Tabelle | Partitionen | Typ | mögliche Schlüssel | Schlüssel | Schlüssellänge | Ref. | Zeilen | gefiltert | Extra | +----+----------+--------+------------+-------+---------------+--------+---------+---------+----------+-------+----------+-------------+ | 1 | LÖSCHEN | test01 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100,00 | NULL | | 1 | SIMPLE | test02 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | Index wird verwendet | +----+----------+--------+------------+-------+---------------+--------+---------+---------+----------+-------+----------+-------------+ 2 Zeilen im Satz (0,01 Sek.) root@localhost [dbtest01]> desc lösche test01.* von test01, führe test02 mit test01.a=test02.b und test02.id=350000 zusammen; +----+----------+--------+------------+-------+---------------+--------+---------+---------+-------+------+------+------+------+------+ | ID | Auswahltyp | Tabelle | Partitionen | Typ | mögliche Schlüssel | Schlüssel | Schlüssellänge | Ref. | Zeilen | gefiltert | Extra | +----+----------+--------+------------+-------+---------------+--------+---------+---------+-------+------+------+------+------+------+ | 1 | EINFACH | test02 | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100,00 | NULL | | 1 | LÖSCHEN | test01 | NULL | ref | a | a | 5 | const | 1 | 100,00 | NULL | +----+----------+--------+------------+-------+---------------+--------+---------+---------+-------+------+------+------+------+------+ 2 Zeilen im Satz (0,00 Sek.) Referenzdokumente: https://www.cnblogs.com/zhengyun_ustc/p/slowquery1.html Dies ist das Ende dieses Artikels über die Implementierung von MySQL Select in der Unterabfrageoptimierung. Weitere relevante Inhalte zu MySQL Select in der Unterabfrageoptimierung finden Sie in den vorherigen Artikeln von 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, dass jeder 123WORDPRESS.COM in Zukunft unterstützen wird! Das könnte Sie auch interessieren:
|
<<: Detaillierte Erläuterung des Docker-Datensicherungs- und Wiederherstellungsprozesses
>>: Rendering-Funktion und JSX-Details
1. Was ist Semantisierung? Erklärung zum Bing-Wör...
In diesem Artikel erfahren Sie mehr über eine zus...
Schauen wir uns zunächst ein Beispiel an Code kopi...
Ein Datenbankindex ist eine Datenstruktur, deren ...
Fehler: Connection to blog0@localhost failed. [08...
1. Einführung in VMware vSphere VMware vSphere is...
Inhaltsverzeichnis 1. Teilzeichenfolge () 2. subs...
Erstellen eines Cursors Erstellen Sie zunächst ei...
Inhaltsverzeichnis 1. Hintergrund zur Umsetzung 2...
Inhaltsverzeichnis 1. Generieren Sie einen abstra...
MYSQL bietet offiziell eine Installer-Methode zum...
Dieser Artikel stellt die Entwicklungsumgebung vo...
Inhaltsverzeichnis eins. Umfeld zwei. Vorsichtsma...
Inhaltsverzeichnis WXS-Antwortereignis Plan A Sei...
Ich bin einmal auf eine Aufgabe gestoßen, bei der...