Das EXPLAIN von MySQL lesen: possible_keys, key, Extra und die Spaltenreihenfolge eines Index
Mit KI übersetzt, damit alle es lesen können; die Originalsprache ist Spanisch. Original lesen
Am Anfang meiner Laufbahn als DBA habe ich mich mit langsamen Abfragen sehr geplagt. Ich sah, dass eine Abfrage lange dauerte, wusste aber nicht, wie ich herausfinden sollte, was sich daran optimieren ließ. Mit der Zeit habe ich gelernt, EXPLAIN zu lesen, mit dem man die Datenbank-Engine fragt, wie sie eine Abfrage ausführen will, bevor sie es tut. Dieser Beitrag ist das, was ich mir damals von jemandem erklärt gewünscht hätte.
Anders als der vorige geht es hier nur um MySQL. MariaDB, das aus MySQL hervorgegangen ist, liefert fast dieselbe Tabelle, und wo sie sich unterscheidet, sage ich es.
Die Tabellen der Beispiele
Zweihunderttausend Bestellungen von fünftausend Kunden: 90 % geliefert, 7 % versandt und 3 % offen, mit Daten zwischen 2022 und 2025. Die Zeilen stammen aus einer Reihe, die mit einem rekursiven CTE erzeugt wird; erklärt habe ich das im Beitrag darüber, wie man eine Tabelle mit Testdaten füllt:
CREATE TABLE clientes (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(100) NOT NULL,
pais CHAR(2) NOT NULL
);
CREATE TABLE pedidos (
id INT AUTO_INCREMENT PRIMARY KEY,
cliente_id INT NOT NULL,
estado VARCHAR(20) NOT NULL,
fecha DATE NOT NULL,
total DECIMAL(10,2) NOT NULL
);
SET SESSION cte_max_recursion_depth = 200000;
INSERT INTO clientes (nombre, pais)
WITH RECURSIVE serie (n) AS (
SELECT 1
UNION ALL
SELECT n + 1 FROM serie WHERE n < 5000
)
SELECT CONCAT('Cliente ', n), ELT(1 + n % 5, 'CR', 'MX', 'ES', 'AR', 'CO')
FROM serie;
INSERT INTO pedidos (cliente_id, estado, fecha, total)
WITH RECURSIVE serie (n) AS (
SELECT 1
UNION ALL
SELECT n + 1 FROM serie WHERE n < 200000
)
SELECT 1 + FLOOR(RAND() * 5000),
CASE WHEN n % 100 < 90 THEN 'entregado'
WHEN n % 100 < 97 THEN 'enviado'
ELSE 'pendiente' END,
DATE '2022-01-01' + INTERVAL FLOOR(RAND() * 1400) DAY,
ROUND(RAND() * 1000, 2)
FROM serie;In MariaDB heißt die Variable im SET max_recursive_iterations.
Vorerst hat pedidos nur ihren Primärschlüssel. Die Indizes lege ich unterwegs an, um zu sehen, was jeder einzelne am Plan ändert. Die Daten sind zufällig: Wenn du die Beispiele wiederholst, werden deine Werte in rows nicht genau meine sein. Die Ausgaben stammen von MySQL 8.0.
MySQL nach dem Plan fragen
Es genügt, EXPLAIN vor das SELECT zu schreiben:
EXPLAIN SELECT * FROM pedidos WHERE cliente_id = 1234;+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| 1 | SIMPLE | pedidos | NULL | ALL | NULL | NULL | NULL | NULL | 200000 | 10.00 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+--------+----------+-------------+EXPLAIN führt die Abfrage nicht aus: Es liefert den Plan, mit einer Zeile für jede Tabelle, die die Abfrage liest. Was jede Spalte sagt:
idundselect_type: zu welchemSELECTdie Zeile gehört. Sie zählen bei Unterabfragen oderUNION; in einer einfachen Abfrage sind sie1undSIMPLE.table: die Tabelle oder ihr Alias.type: wie auf sie zugegriffen wird. Vom besten zum schlechtesten sind die häufigstenconst(eine einzige Zeile, über den Schlüssel),eq_ref(eine Zeile pro Zeile der vorigen Tabelle),ref(die Zeilen mit einem Wert des Index),range(ein Abschnitt des Index),index(der ganze Index) undALL(die ganze Tabelle).possible_keysundkey: die Indizes, die helfen könnten, und der gewählte. Sie haben weiter unten einen eigenen Abschnitt.key_len: wie viele Bytes des Index benutzt werden; bei einem Index über mehrere Spalten sagt es, wie viele Spalten benutzt wurden.ref: womit der Index verglichen wird: mit einer Konstante (const) oder einer Spalte einer anderen Tabelle.rows: wie viele Zeilen voraussichtlich geprüft werden. Das ist eine Schätzung aus den Statistiken der Tabelle, keine Zählung.filtered: welcher Anteil dieser Zeilen voraussichtlich dasWHEREübersteht.Extra: was in die anderen Spalten nicht passt: ob gefiltert wird, ob sortiert wird, ob eine temporäre Tabelle entsteht.
Hier ist type gleich ALL, key ist leer und rows ist 200.000: Um die 40 Bestellungen eines Kunden zu liefern, liest MySQL alle zweihunderttausend.
Wenn dein MySQL statt dieser Tabelle einen Textbaum liefert, steht die Variable explain_format (es gibt sie seit MySQL 8.0.32) auf TREE; das neueste MySQL in meinem Testbed ist standardmäßig so eingestellt. EXPLAIN FORMAT=TRADITIONAL fordert die Tabelle an.
possible_keys und key: Indizes zu haben heißt nicht, sie zu benutzen
Das sind die zwei Spalten, die ich am schwersten verstanden habe. possible_keys sind die Indizes, die der Optimierer für die Abfrage in Betracht zieht; key ist der, den er wirklich benutzt. Ich kann zehn Indizes in possible_keys haben: Steht in key keiner, benutzt die Abfrage keinen Index.
Es gibt zwei Gründe, warum key leer bleibt. Der erste: Kein Index passt, und dann ist auch possible_keys leer, wie im Beispiel oben. Der zweite: Es gibt einen, und der Optimierer entscheidet, ihn nicht zu benutzen. Um das zu sehen, lege ich einen Index auf das Datum an:
CREATE INDEX idx_fecha ON pedidos (fecha);Die Bestellungen eines Monats benutzen ihn:
EXPLAIN SELECT * FROM pedidos
WHERE fecha >= '2025-10-01' AND fecha < '2025-11-01';+-------+---------------+-----------+---------+------+------+----------+-----------------------+
| type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+-------+---------------+-----------+---------+------+------+----------+-----------------------+
| range | idx_fecha | idx_fecha | 3 | NULL | 4399 | 100.00 | Using index condition |
+-------+---------------+-----------+---------+------+------+----------+-----------------------+Die eines ganzen Jahres nicht:
EXPLAIN SELECT * FROM pedidos
WHERE fecha >= '2024-01-01' AND fecha < '2025-01-01';+------+---------------+------+---------+------+--------+----------+-------------+
| type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+------+---------------+------+---------+------+--------+----------+-------------+
| ALL | idx_fecha | NULL | NULL | NULL | 200000 | 50.00 | Using where |
+------+---------------+------+---------+------+--------+----------+-------------+idx_fecha steht in possible_keys, aber key ist leer und type ist wieder ALL. Ein Jahr ist ein Viertel der Bestellungen, und ein Viertel der Tabelle sprunghaft zu lesen, jede Zeile vom Index aus gesucht, kostet mehr, als die ganze Tabelle der Reihe nach zu lesen. Das ist kein Fehler, sondern eine Entscheidung, und manchmal die richtige. Sieh dir auch filtered an: Es schätzt 50 %, während der echte Anteil 26 % beträgt. Es sind Schätzungen, und sie können danebenliegen.
Die häufigste Falle ist eine andere: den Index zu haben und die Bedingung so zu schreiben, dass er nicht benutzt werden kann. Dieselben Oktober-Bestellungen, nach Jahr und Monat abgefragt:
EXPLAIN SELECT * FROM pedidos
WHERE YEAR(fecha) = 2025 AND MONTH(fecha) = 10;+------+---------------+------+---------+------+--------+----------+-------------+
| type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+------+---------------+------+---------+------+--------+----------+-------------+
| ALL | NULL | NULL | NULL | NULL | 200000 | 100.00 | Using where |
+------+---------------+------+---------+------+--------+----------+-------------+Jetzt ist possible_keys leer. Der Index speichert die Daten sortiert, nicht ihre Jahre oder Monate, und um zu wissen, welche Daten YEAR(fecha) = 2025 erfüllen, muss MySQL es Zeile für Zeile berechnen. Dasselbe gilt für jede Funktion oder Rechnung auf der Spalte: DATE(fecha), LOWER(email), total * 1.13. Der Ausweg ist, die Bedingung als Bereich auf der Spalte zu schreiben, wie im ersten Oktober-Beispiel.
Die Spalte Extra
Sie ist die letzte Spalte und die, die am meisten verrät, wenn alles andere in Ordnung scheint. Die Werte, die mir am häufigsten begegnen:
Using where: DasWHEREwird auf die Zeilen angewandt, nachdem sie gelesen wurden. MittypegleichALLheißt das, dass die ganze Tabelle gelesen und verworfen wird, was nicht passt.Using index: Alles, was die Abfrage verlangt, steht im Index, also wird die Tabelle gar nicht gelesen. Das ist das Beste, was hier stehen kann.Using index condition: Ein Teil desWHEREwird im Index geprüft, bevor die Zeile geholt wird.Using filesort: Das Ergebnis muss gesondert sortiert werden, weil es nicht schon sortiert aus einem Index gelesen wird. Der Name täuscht: Es heißt nicht, dass auf die Festplatte geschrieben wird.Using temporary: Eine temporäre Tabelle wird angelegt, um die Abfrage aufzulösen, typischerweise bei einemGROUP BYund einemORDER BYüber verschiedene Spalten.
Die Bestellungen im Oktober zu zählen geht, ohne die Tabelle anzufassen, weil das Datum im Index steht und sonst nichts gebraucht wird:
EXPLAIN SELECT COUNT(*) FROM pedidos
WHERE fecha >= '2025-10-01' AND fecha < '2025-11-01';+-------+---------------+-----------+---------+------+------+----------+--------------------------+
| type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+-------+---------------+-----------+---------+------+------+----------+--------------------------+
| range | idx_fecha | idx_fecha | 3 | NULL | 4399 | 100.00 | Using where; Using index |
+-------+---------------+-----------+---------+------+------+----------+--------------------------+Die Bestellungen eines Tages, von der teuersten zur billigsten, werden über den Datumsindex gelesen, müssen aber nach dem Betrag sortiert werden:
EXPLAIN SELECT * FROM pedidos
WHERE fecha = '2024-05-10'
ORDER BY total DESC;+------+---------------+-----------+---------+-------+------+----------+----------------+
| type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+------+---------------+-----------+---------+-------+------+----------+----------------+
| ref | idx_fecha | idx_fecha | 3 | const | 124 | 100.00 | Using filesort |
+------+---------------+-----------+---------+-------+------+----------+----------------+Und die zehn Kunden, die im Oktober am meisten ausgegeben haben, brauchen beides: eine temporäre Tabelle, um pro Kunde zu summieren, und eine Sortierung nach der Summe:
EXPLAIN SELECT cliente_id, SUM(total) AS gastado
FROM pedidos
WHERE fecha >= '2025-10-01' AND fecha < '2025-11-01'
GROUP BY cliente_id
ORDER BY gastado DESC
LIMIT 10;+-------+---------------+-----------+---------+------+------+----------+--------------------------------------------------------+
| type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+-------+---------------+-----------+---------+------+------+----------+--------------------------------------------------------+
| range | idx_fecha | idx_fecha | 3 | NULL | 4399 | 100.00 | Using index condition; Using temporary; Using filesort |
+-------+---------------+-----------+---------+------+------+----------+--------------------------------------------------------+Keines von beiden ist an sich schlecht. Ich achte darauf, ob sie in einer Abfrage auftauchen, die tausendmal am Tag läuft, oder über viele Zeilen.
Wenn die Abfrage mehrere Tabellen liest, hat der Plan eine Zeile pro Tabelle, in der Reihenfolge, in der sie gelesen werden:
EXPLAIN SELECT c.nombre, p.fecha, p.total
FROM pedidos AS p
JOIN clientes AS c ON c.id = p.cliente_id
WHERE p.fecha = '2024-05-10';+-------+--------+---------------+-----------+---------+--------------------------------+------+----------+-------+
| table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+-------+--------+---------------+-----------+---------+--------------------------------+------+----------+-------+
| p | ref | idx_fecha | idx_fecha | 3 | const | 124 | 100.00 | NULL |
| c | eq_ref | PRIMARY | PRIMARY | 4 | tienda.p.cliente_id | 1 | 100.00 | NULL |
+-------+--------+---------------+-----------+---------+--------------------------------+------+----------+-------+Zuerst die Bestellungen des Tages, über idx_fecha; und für jede davon ihr Kunde über den Primärschlüssel (eq_ref), verglichen mit p.cliente_id, wie ref sagt.
Die Spaltenreihenfolge eines Index
Ein Index kann mehrere Spalten haben. Ich lege einen mit dreien an, gedacht für die Suche nach den Bestellungen eines Kunden in einem Status und an einem Datum:
CREATE INDEX idx_cliente_estado_fecha ON pedidos (cliente_id, estado, fecha);Mit allen drei Spalten im WHERE benutzt er ihn ganz:
EXPLAIN SELECT * FROM pedidos
WHERE cliente_id = 1234 AND estado = 'pendiente' AND fecha = '2024-05-10';+------+------------------------------------+--------------------------+---------+-------------------+------+----------+-------+
| type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+------+------------------------------------+--------------------------+---------+-------------------+------+----------+-------+
| ref | idx_fecha,idx_cliente_estado_fecha | idx_cliente_estado_fecha | 89 | const,const,const | 1 | 100.00 | NULL |
+------+------------------------------------+--------------------------+---------+-------------------+------+----------+-------+Lange habe ich geglaubt, die Bedingungen im WHERE müssten in derselben Reihenfolge stehen wie die Spalten des Index, und sonst tauge der Index nichts. Ich lag falsch: Der Optimierer ordnet jede Bedingung ihrer Spalte zu, egal wo sie steht. Mit umgekehrter Reihenfolge ist der Plan identisch:
EXPLAIN SELECT * FROM pedidos
WHERE fecha = '2024-05-10' AND estado = 'pendiente' AND cliente_id = 1234;+------+------------------------------------+--------------------------+---------+-------------------+------+----------+-------+
| type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+------+------------------------------------+--------------------------+---------+-------------------+------+----------+-------+
| ref | idx_fecha,idx_cliente_estado_fecha | idx_cliente_estado_fecha | 89 | const,const,const | 1 | 100.00 | NULL |
+------+------------------------------------+--------------------------+---------+-------------------+------+----------+-------+Was dagegen zählt, ist die Reihenfolge der Spalten im Index. Ein Index über mehrere Spalten ist nach der ersten sortiert; innerhalb jedes Werts der ersten nach der zweiten; und so weiter. Er ist wie ein Telefonbuch, das nach Nachname und Vorname sortiert ist: Man findet darin einen Nachnamen oder einen Nachnamen mit Vornamen, aber nicht alle, die Ana heißen. Das MySQL-Handbuch nennt das das linke Präfix: Ein Index über (cliente_id, estado, fecha) taugt für die Suche nach cliente_id, nach cliente_id und estado oder nach allen dreien. Für die Suche nach estado oder fecha ohne cliente_id taugt er nicht.
Die offenen Bestellungen dieses Jahres, ohne Angabe des Kunden:
EXPLAIN SELECT * FROM pedidos
WHERE estado = 'pendiente' AND fecha >= '2025-01-01';+------+---------------+------+---------+------+--------+----------+-------------+
| type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+------+---------------+------+---------+------+--------+----------+-------------+
| ALL | idx_fecha | NULL | NULL | NULL | 199728 | 4.34 | Using where |
+------+---------------+------+---------+------+--------+----------+-------------+Die Tabelle hat einen Index, der estado und fecha enthält, aber er beginnt mit cliente_id und taucht nicht einmal in possible_keys auf. Dort steht nur idx_fecha, den der Optimierer aus demselben Grund wie vorhin verwirft: fast ein Jahr an Bestellungen.
key_len: wie viele Spalten benutzt wurden
Hat der Index mehrere Spalten, sagt key_len, wie weit er gekommen ist. Jede Spalte belegt eine feste Zahl von Bytes im Index: cliente_id ist ein INT, 4 Bytes; estado ist ein VARCHAR(20) in utf8mb4, 20 Zeichen mit bis zu 4 Bytes plus 2 für die Länge, also 82; und fecha ist ein DATE, 3. Alle drei zusammen ergeben die 89 von oben. Eine Spalte, die NULL zulässt, kostet ein Byte mehr.
Mit Kunde und Status, ohne Datum, benutzt er die ersten zwei Spalten, 4 + 82:
EXPLAIN SELECT * FROM pedidos
WHERE cliente_id = 1234 AND estado = 'pendiente';+------+--------------------------+--------------------------+---------+-------------+------+----------+-------+
| type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+------+--------------------------+--------------------------+---------+-------------+------+----------+-------+
| ref | idx_cliente_estado_fecha | idx_cliente_estado_fecha | 86 | const,const | 1 | 100.00 | NULL |
+------+--------------------------+--------------------------+---------+-------------+------+----------+-------+Mit Kunde und Datum, unter Auslassung des Status, kann er nur die erste benutzen:
EXPLAIN SELECT * FROM pedidos
WHERE cliente_id = 1234 AND fecha >= '2024-01-01';+------+------------------------------------+--------------------------+---------+-------+------+----------+-----------------------+
| type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+------+------------------------------------+--------------------------+---------+-------+------+----------+-----------------------+
| ref | idx_fecha,idx_cliente_estado_fecha | idx_cliente_estado_fecha | 4 | const | 40 | 50.00 | Using index condition |
+------+------------------------------------+--------------------------+---------+-------+------+----------+-----------------------+key sagt idx_cliente_estado_fecha, und wer nur auf diese Spalte schaut, würde meinen, der Index arbeite vollständig. key_len sagt 4: Er sucht nach cliente_id und nach nichts sonst. Das Datum wird im Index geprüft (Using index condition), grenzt die Suche aber nicht ein, weil zwischen Kunde und Datum der Status steht.
Wenn key einen Namen hat und trotzdem alles gelesen wird
Einen Fall sollte man noch kennen. Verlangt die Abfrage nur Spalten, die im Index stehen, kann MySQL statt der Tabelle den ganzen Index lesen, auch wenn der Filter nicht mit der ersten Spalte beginnt:
EXPLAIN SELECT cliente_id, estado, fecha FROM pedidos
WHERE estado = 'pendiente';+-------+--------------------------+--------------------------+---------+------+--------+----------+--------------------------+
| type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+-------+--------------------------+--------------------------+---------+------+--------+----------+--------------------------+
| index | idx_cliente_estado_fecha | idx_cliente_estado_fecha | 89 | NULL | 199728 | 10.00 | Using where; Using index |
+-------+--------------------------+--------------------------+---------+------+--------+----------+--------------------------+key hat einen Namen, aber type ist index und rows ist 199.728: Es werden alle zweihunderttausend Einträge des Index durchlaufen, einer nach dem anderen. Das ist schneller, als die Tabelle zu durchlaufen, weil der Index kleiner ist, aber es ist keine Suche. In MariaDB kommt derselbe Plan mit leerem possible_keys und benanntem key heraus, und das ist genau der Fall, den das MySQL-Handbuch beschreibt: Der Index hilft nicht, die Zeilen zu finden, enthält aber alle verlangten Spalten. Deshalb liest man key nie allein, sondern immer zusammen mit type und rows.
Die Lösung
Wenn die häufige Abfrage nach Status und Datum geht, muss der Index mit dem Status beginnen:
CREATE INDEX idx_estado_fecha ON pedidos (estado, fecha);EXPLAIN SELECT * FROM pedidos
WHERE estado = 'pendiente' AND fecha >= '2025-01-01';+-------+----------------------------+------------------+---------+------+------+----------+-----------------------+
| type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+-------+----------------------------+------------------+---------+------+------+----------+-----------------------+
| range | idx_fecha,idx_estado_fecha | idx_estado_fecha | 85 | NULL | 1321 | 100.00 | Using index condition |
+-------+----------------------------+------------------+---------+------+------+----------+-----------------------+Von 199.728 Zeilen auf 1.321, und key_len 85: der ganze Status, 82, plus das Datum, 3. Die Regel, nach der ich die Reihenfolge festlege: zuerst die Spalten, die mit = verglichen werden, und zuletzt die, die mit einem Bereich verglichen wird (>=, BETWEEN). Nach einem Bereich grenzen die folgenden Spalten des Index die Suche nicht mehr ein.
Ist es in anderen Engines genauso?
Ja. Das linke Präfix ist eine Eigenschaft von B-Tree-Indizes, die in allen relationalen Engines der Standard sind, und die Dokumentation von PostgreSQL, SQLite und Oracle erklärt es genauso. In keiner spielt es eine Rolle, in welcher Reihenfolge man die Bedingungen des WHERE schreibt.
Was manche über die Jahre gelernt haben, ist, die erste Spalte zu überspringen, wenn sie sehr wenige verschiedene Werte hat: Sie suchen nach der zweiten, einmal für jeden Wert der ersten. Das heißt Skip Scan, und es gibt ihn in MySQL seit 8.0.13, in PostgreSQL seit 18, in Oracle und in SQLite (in SQLite nur, wenn ANALYZE ausgeführt wurde). In MySQL erscheint er in Extra als Using index for skip scan, und nur, wenn die Abfrage eine einzige Tabelle liest und ausschließlich Spalten des Index verlangt, neben weiteren Bedingungen. Hier hätte er nichts gebracht: Die erste Spalte ist der Kunde, und davon gibt es fünftausend.
Messen statt schätzen
Alles bisher waren Schätzungen: EXPLAIN führt die Abfrage nicht aus. Seit MySQL 8.0.18 führt EXPLAIN ANALYZE sie aus und setzt neben jede Schätzung, was wirklich passiert ist:
EXPLAIN ANALYZE SELECT * FROM pedidos
WHERE estado = 'pendiente' AND fecha >= '2025-01-01';-> Index range scan on pedidos using idx_estado_fecha over (estado = 'pendiente' AND '2025-01-01' <= fecha), with index condition: ((pedidos.estado = 'pendiente') and (pedidos.fecha >= DATE'2025-01-01')) (cost=595 rows=1321) (actual time=1.01..12.6 rows=1321 loops=1)rows=1321 in der ersten Klammer ist die Schätzung; actual time und rows=1321 in der zweiten sind die Dauer in Millisekunden und die gelesenen Zeilen. Liegen die beiden Zeilenzahlen weit auseinander, sind die Statistiken der Tabelle veraltet, und ANALYZE TABLE pedidos berechnet sie neu.
MariaDB hat kein EXPLAIN ANALYZE. Die Entsprechung ist ANALYZE vor der Abfrage, seit 10.1, das die übliche Tabelle mit zwei zusätzlichen Spalten liefert, r_rows und r_filtered, mit dem, was passiert ist:
ANALYZE SELECT * FROM pedidos
WHERE estado = 'pendiente' AND fecha >= '2025-01-01';Beide führen die Abfrage aus, dauern also so lange wie sie. Und in MariaDB ändert ein ANALYZE UPDATE oder ANALYZE DELETE die Daten wirklich.
In Calíope
Im SQL-Editor fragt die Schaltfläche Visual EXPLAIN in der Leiste, sobald die Abfrage geschrieben ist, den Server nach dem Plan, ohne die Abfrage auszuführen, und zeichnet ihn als Baum, mit einer Farbe pro Zugriffsart: Rot für das vollständige Durchlaufen einer Tabelle, Orange für das eines ganzen Index, Gelb für einen Abschnitt eines Index und Grün für eine Suche über den Schlüssel. Calíope fordert den Plan als JSON an, also hängt es nicht davon ab, wie dein explain_format eingestellt ist. Und die Schaltfläche Indizes in der Ergebnisleiste öffnet den Index-Berater, der in diesem Plan nach vollständigen Durchläufen sucht und, wenn die Abfrage nach einer Spalte filtert, die kein Index abdeckt, das CREATE INDEX vorschlägt.
Worauf ich zuerst schaue
Wenn eine Abfrage langsam ist, lese ich ihr EXPLAIN in dieser Reihenfolge:
key: Ist es leer, wird kein Index benutzt. Steht inpossible_keyseiner, hat der Optimierer ihn verworfen; ist auch das leer, passt keiner, oder die Bedingung lässt ihn nicht zu.typeundrowszusammen:ALLoderindexmit vielen Zeilen heißt, die ganze Tabelle oder den ganzen Index zu lesen, auch wennkeyeinen Namen hat.key_len, wenn der Index mehrere Spalten hat: Es sagt, wie viele wirklich benutzt wurden.Extra:Using temporaryundUsing filesortin einer Abfrage, die oft läuft, verdienen einen zweiten Blick.