Warum wählt MySQL diesen Ausführungsplan?

7

Ich habe zwei Fragen,

select some_other_column 
from `table` 
order by primary_index_column asc 
limit 4000000, 10;

und

select some_other_column 
from `table` 
order by secondary_index_column asc 
limit 4000000, 10;

Beide geben 10 Zeilen zurück; Der erste dauert 2,74 Sekunden und der zweite 7,07 Sekunden. some_other_columnist kein Teil eines Index. primary_index_columnist die Primärschlüsselspalte; secondary_index_columnhat einen B-Tree-Index und eine Kardinalität von 200 (laut MySQL).

Hier sind die explainErgebnisse:

mysql> explain select some_other_column from `table` order by primary_index_column limit 4000000, 10;
+----+-------------+---------+-------+---------------+---------+---------+------+---------+-------+
| id | select_type | table   | type  | possible_keys | key     | key_len | ref  | rows    | Extra |
+----+-------------+---------+-------+---------------+---------+---------+------+---------+-------+
|  1 | SIMPLE      | table   | index | NULL          | PRIMARY | 4       | NULL | 4000010 |       |
+----+-------------+---------+-------+---------------+---------+---------+------+---------+-------+

mysql> explain select some_other_column from `table` order by secondary_index_column limit 4000000, 10;
+----+-------------+---------+------+---------------+------+---------+------+---------+----------------+
| id | select_type | table   | type | possible_keys | key  | key_len | ref  | rows    | Extra          |
+----+-------------+---------+------+---------------+------+---------+------+---------+----------------+
|  1 | SIMPLE      | table   | ALL  | NULL          | NULL | NULL    | NULL | 4642945 | Using filesort |
+----+-------------+---------+------+---------------+------+---------+------+---------+----------------+

Warum wählt MySQL diesen spezifischen Ausführungsplan für die zweite Abfrage? Ich verstehe nicht, warum es den Index für die erste Abfrage verwenden kann, aber nicht für die zweite Abfrage.

Matt Fenwick
quelle

Antworten:

7

An eine indizierte Spalte in InnoDB ist immer ein zusätzlicher Schlüssel zum gen_clust_index (auch bekannt als Clustered Index) angehängt. Dies würde von der ersten Abfrage durchlaufen, um in der Reihenfolge des Index zur Zeile 4000000 zu gelangen. Da dies die einzige angeforderte Spalte ist, ist kein Zugriff auf die Tabelle erforderlich.

Die zweite Abfrage muss die nicht indizierte Spalte aus der Tabelle zusammen mit der indizierten Spalte in einer temporären Tabelle sammeln. Die temporäre Tabelle wird dann sortiert, bevor die nicht indizierte Spalte als SELECT-Ausgabe angezeigt wird.

Beachten Sie einen anderen Kontrast

  • Die Tabellenanzahl beträgt 4636881
  • Der EXPLAIN-Plan für die erste Abfrage hat 4000010 indexed_column-Schlüssel durchlaufen. Die letzten 636871-Schlüssel müssen nicht gelesen werden.
  • Der EXPLAIN-Plan für die zweite Abfrage durchlief 4636881 Zeilen, sortiert nach indexed_column. Für jede Zeile, die die nicht indizierte Spalte aus der Tabelle aufnimmt, wird die indizierte Spalte (bereits nach dem Index sortiert) nachgeschlagen und kommt für die Fahrt mit. Die tmp-Tabelle wird nach der indizierten Spalte sortiert, und mysqld schließt dann die ersten 4000000 Zeilen ab, wobei 10 Zeilen übrig bleiben. Diese Interaktion zwischen Tabelle und Index nur für 10 Zeilen ist der Engpass.

ALLGEMEINE DINGE

In beiden Fällen gibt die Abfrage die Anzahl der zu durchlaufenden Zeilen an. Da die Anzahl der Zeilen in der Tabelle 4636881 beträgt, sollten wir ohne weiteres einen vollständigen Scan erwarten. Der Kontrast wird deutlich, wenn das MySQL Query Optimizer entscheidet, wo der vollständige Scan durchgeführt werden soll.

  • Die erste Abfrage verweist nur auf eine indizierte Spalte in der SELECT-Liste und der WHERE-Klausel. Das MySQL Query Optimizer führt einen vollständigen Index-Scan durch, ohne die Tabelle kontaktieren zu müssen, da sich alles Notwendige im Index befindet.
  • Die zweite Abfrage verweist auf eine indizierte Spalte in der WHERE-Klausel. Es muss jedoch die Tabelle erreichen, um die entsprechende nicht indizierte Spalte abzurufen. Der MySQL Query Optmizer wurde durch die Abfrage darauf hingewiesen, dass er den Index aufgrund der Anzahl der Zeilen, die er lesen sollte, nicht verwenden darf . Als Faustregel für jedes RDBMS gilt: Wenn mehr als 5% einer Tabelle gelesen werden müssen, um eine Abfrage zu erfüllen, wirft das MySQL Query Optimizer einfach den Index "unter den Bus" und führt einen vollständigen Tabellenscan durch .

Wenn Sie rechnen, berechnet MySQL Query Optimizer Folgendes:

  • 5% von 4636881 sind 231844
  • Die zweite Abfrage wird angewiesen, 4000000 Zeilen zu lesen, was weit über 231844 liegt
  • MySQL Query Optimizer erkennt, dass zwischen Tabelle (aufgrund der nicht indizierten Spalte) und Index (aufgrund der indizierten Spalte) zu viele Interaktionen bestehen, um die erforderlichen Daten abzurufen. Es wird beschlossen, nur die Tabelle zu lesen (da sich sowohl die indizierten als auch die nicht indizierten Spalten zusammen in der Tabelle befinden), anstatt zwischen ihnen hin und her zu springen.

Meiner ehrlichen Meinung nach hat der MySQL Query Optimizer mit der Zeilenanzahl der Tabelle, den aktuellen Indizes der Tabelle und der Anzahl der von der Abfrage vorgeschriebenen Zeilen die richtige Entscheidung getroffen .

EMPFEHLUNG

Erstellen Sie diesen Index

ALTER TABLE `table` ADD INDEX mynewndx (indexed_column,some_other_column);

und Ihre zweite Abfrage wird die Tabelle in Zukunft nie wieder berühren. Das MySQL Query Optimizer verhält sich ganz anders, wenn es diesen neuen Index sieht.

RolandoMySQLDBA
quelle
Tatsächlich liest es alle 4636881 Zeilen zuerst während des Dateisortierens, wie im EXPLAIN-Plan gezeigt. Der SELECT führt das Ditching durch, indem er am Ende nur die 10 gewünschten Zeilen erhält.
RolandoMySQLDBA
Sie finden nicht dieselben Zeilen, da die erste Abfrage nur den Clustered-Index liest und NIEMALS DIE TABELLE BERÜHRT. Die zweite Abfrage muss den Index für jede Zeile in den Tabellen kontaktieren. Einfach ausgedrückt muss die zweite Abfrage die Tabelle und den Index lesen.
RolandoMySQLDBA
Tatsächlich sind die EXPLAIN-Pläne für beide Abfragen für mich absolut sinnvoll. Es ist, als müsste ein Schwimmer nur den Ozean respektieren, in dem er schwimmt. Ich vertraue den Zahlen, die er meldet, basierend auf der Anzahl der Zeilen in der Tabelle und basierend auf den aufgerufenen Spalten. Tatsächlich habe ich eine Empfehlung und werde sie meiner Antwort hinzufügen.
RolandoMySQLDBA
@Matt: Beide Abfragen sind möglicherweise besser (und jede Abfrage in diesem Fall ist es das Optimierungsmittel, um dies zu entscheiden), keinen Index zu verwenden, sondern nur die vollständige Tabelle und den Dateisort zu lesen. Ihre Abfragen haben diesen LIMIT 10 OFFSET 4000000Teil, was bedeutet, dass die SQL-Engine irgendwie (indexiert oder anderweitig) 4 Millionen (und 10) Zeilen abrufen und die ersten (!) 4 Millionen davon verwerfen muss. Seien Sie nicht überrascht, wenn die Verwendung des Index nicht oder nur langsam erfolgt.
Ypercubeᵀᴹ
Ich habe meine Antwort mit aktualisiert COMMON THINGS, um das Optimierungsverhalten hinter Ihren Abfragen zu erläutern.
RolandoMySQLDBA
0

Gemäß der MySQL - Dokumentation auf der Optimierung von order byAbfragen ,

In einigen Fällen kann MySQL keine Indizes verwenden, um die ORDER BY [...] aufzulösen. Diese Fälle umfassen Folgendes:

  • [...]
  • Der verwendete Tabellenindextyp speichert die Zeilen nicht in der richtigen Reihenfolge. Dies gilt beispielsweise für einen HASH-Index in einer MEMORY-Tabelle.

Nach meinem Verständnis von InnoDB werden die Zeilen in der Reihenfolge des Primärschlüssels gespeichert. Daher sind sie für Sekundärindizes nicht in Ordnung.

Matt Fenwick
quelle
Das ist keine gute Erklärung dafür, warum der Sekundärindex nicht verwendet wird.
Ypercubeᵀᴹ
@ypercube was ist das problem
Matt Fenwick
Ich meine, dass Informationen in der Tabelle (gemäß dem Clustering-Index, der normalerweise die PK ist) und in den Sekundärindizes gespeichert sind. Wenn Sie meinen, dass die some_other_columnInformationen nicht im secondary_index_columnIndex gespeichert sind , klicken Sie auf OK. Das erklärt, warum ein (Deckungs-) Index von (secondary_index_column, some_other_column), wie von Rolando vorgeschlagen, die Informationen enthält und hilfreich sein kann.
Ypercubeᵀᴹ