Müssen tatsächlich alle ausgewählten Spalten indiziert werden, damit MySQL den Index verwenden kann?
Dies ist eine geladene Frage, da es Faktoren gibt, die bestimmen, ob ein Index die Verwendung wert ist.
FAKTOR 1
Wie lautet die Schlüsselpopulation für einen bestimmten Index? Mit anderen Worten, wie hoch ist die Kardinalität (eindeutige Anzahl) aller im Index erfassten Tupel?
FAKTOR 2
Welche Speicher-Engine verwenden Sie? Sind alle benötigten Spalten über einen Index zugänglich?
WAS KOMMT ALS NÄCHSTES ???
Nehmen wir ein einfaches Beispiel: Eine Tabelle mit zwei Werten (männlich und weiblich)
Lassen Sie eine solche Tabelle mit einem Test für die Indexverwendung erstellen
USE test
DROP TABLE IF EXISTS mf;
CREATE TABLE mf
(
id int not null auto_increment,
gender char(1),
primary key (id),
key (gender)
) ENGINE=InnODB;
INSERT INTO mf (gender) VALUES
('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
('M'),('M'),('M'),('M'),('F'),('F'),('M'),('M'),
('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
('F'),('M'),('M'),('M'),('M'),('M'),('M'),('M');
ANALYZE TABLE mf;
EXPLAIN SELECT gender FROM mf WHERE gender='F';
EXPLAIN SELECT gender FROM mf WHERE gender='M';
EXPLAIN SELECT id FROM mf WHERE gender='F';
EXPLAIN SELECT id FROM mf WHERE gender='M';
TEST InnoDB
mysql> USE test
Database changed
mysql> DROP TABLE IF EXISTS mf;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE mf
-> (
-> id int not null auto_increment,
-> gender char(1),
-> primary key (id),
-> key (gender)
-> ) ENGINE=InnoDB;
Query OK, 0 rows affected (0.07 sec)
mysql> INSERT INTO mf (gender) VALUES
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('F'),('F'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('F'),('M'),('M'),('M'),('M'),('M'),('M'),('M');
Query OK, 40 rows affected (0.06 sec)
Records: 40 Duplicates: 0 Warnings: 0
mysql> ANALYZE TABLE mf;
+---------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------+---------+----------+----------+
| test.mf | analyze | status | OK |
+---------+---------+----------+----------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 37 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 37 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql>
TEST MyISAM
mysql> USE test
Database changed
mysql> DROP TABLE IF EXISTS mf;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE TABLE mf
-> (
-> id int not null auto_increment,
-> gender char(1),
-> primary key (id),
-> key (gender)
-> ) ENGINE=MyISAM;
Query OK, 0 rows affected (0.05 sec)
mysql> INSERT INTO mf (gender) VALUES
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('F'),('F'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('M'),('M'),('M'),('M'),('M'),('M'),('M'),('M'),
-> ('F'),('M'),('M'),('M'),('M'),('M'),('M'),('M');
Query OK, 40 rows affected (0.00 sec)
Records: 40 Duplicates: 0 Warnings: 0
mysql> ANALYZE TABLE mf;
+---------+---------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------+---------+----------+----------+
| test.mf | analyze | status | OK |
+---------+---------+----------+----------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT gender FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 36 | Using where; Using index |
+----+-------------+-------+------+---------------+--------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='F';
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
| 1 | SIMPLE | mf | ref | gender | gender | 2 | const | 3 | Using where |
+----+-------------+-------+------+---------------+--------+---------+-------+------+-------------+
1 row in set (0.00 sec)
mysql> EXPLAIN SELECT id FROM mf WHERE gender='M';
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | mf | ALL | gender | NULL | NULL | NULL | 40 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
mysql>
Analyse für InnoDB
Beachten Sie, dass beim Laden der Daten als InnoDB alle vier EXPLAIN
Pläne den gender
Index verwendeten. Der dritte und vierte EXPLAIN
Plan verwendeten den gender
Index, obwohl die angeforderten Daten vorhanden waren id
. Warum? Denn id
in den PRIMARY KEY
und allen Sekundärindizes gibt es Referenzzeiger zurück zum PRIMARY KEY
(über den gen_clust_index ).
Analyse für MyISAM
Beachten Sie, dass die ersten drei EXPLAIN
Pläne den gender
Index verwendeten , als die Daten als MyISAM geladen wurden . Im vierten EXPLAIN
Plan hat das Abfrageoptimierungsprogramm entschieden, überhaupt keinen Index zu verwenden. Stattdessen wurde ein vollständiger Tabellenscan gewählt. Warum?
Unabhängig von DBMS arbeiten die Abfrageoptimierer mit einer sehr einfachen Faustregel: Wenn ein Index als Kandidat für die Durchführung der Suche überprüft wird und das Abfrageoptimierungsprogramm berechnet, dass mehr als 5% der Gesamtzahl der Suchvorgänge ausgeführt werden müssen Zeilen in der Tabelle:
- Ein vollständiger Index-Scan wird durchgeführt, wenn sich alle zum Abrufen erforderlichen Spalten im ausgewählten Index befinden
- ansonsten ein vollständiger Tabellenscan
FAZIT
Wenn Sie nicht die richtigen Deckungsindizes haben oder wenn die Schlüsselpopulation für ein bestimmtes Tupel mehr als 5% der Tabelle beträgt, müssen sechs Dinge geschehen:
- Stellen Sie fest, dass Sie die Abfragen profilieren müssen
- Finden Sie alle
WHERE
, GROUP BY
und ORDER BY` Klauseln aus diesen Abfragen
- Formulieren Sie die Indizes in dieser Reihenfolge
WHERE
Klauselspalten mit statischen Werten
GROUP BY
Säulen
ORDER BY
Säulen
- Vermeiden Sie vollständige Tabellenscans (Abfragen ohne sinnvolle
WHERE
Klausel)
- Vermeiden Sie fehlerhafte Schlüsselpopulationen (oder zwischenspeichern Sie zumindest diese fehlerhaften Schlüsselpopulationen)
- Entscheiden Sie sich für die beste MySQL Storage Engine ( InnoDB oder MyISAM ) für die Tabellen
Ich habe in der Vergangenheit über diese Faustregel von 5% geschrieben:
UPDATE 2012-11-14 13:05 EDT
Ich habe mir Ihre Frage und den ursprünglichen SO-Beitrag noch einmal angesehen . Dann dachte ich an meine, die Analysis for InnoDB
ich zuvor erwähnt hatte. Es fällt mit dem person
Tisch zusammen. Warum?
Für beide Tabellen mf
undperson
- Die Storage Engine ist InnoDB
- Primärschlüssel ist
id
- Der Tabellenzugriff erfolgt über den sekundären Index
- Wenn der Tisch MyISAM wäre, würden wir einen völlig anderen
EXPLAIN
Plan sehen
Nun sehen Sie die Abfrage aus der SO Frage: select * from person order by age\G
. Da es keine WHERE
Klausel gibt, haben Sie ausdrücklich einen vollständigen Tabellenscan verlangt . Die Standardsortierreihenfolge der Tabelle wäre nach id
(PRIMARY KEY), da sie auto_increment und the ist gen_clust_index (aka Clustered Index) nach interner Zeilen-ID sortiert ist . Beachten Sie bei der Sortierung nach Index, dass InnoDB-Sekundärindizes die Zeilen-ID an jeden Indexeintrag angehängt haben. Dies erzeugt jedes Mal den internen Bedarf für vollen Zeilenzugriff.
Einrichten ORDER BY
einer InnoDB-Tabelle kann eine ziemlich entmutigende Aufgabe sein, wenn Sie diese Fakten über die Organisation von InnoDB-Indizes ignorieren.
Zurück zu dieser SO-Abfrage: Da Sie ausdrücklich einen vollständigen Tabellenscan gefordert haben , hat der MySQL Query Optimizer IMHO das Richtige getan (oder zumindest den Pfad des geringsten Widerstands gewählt). Wenn es um InnoDB und die SO-Abfrage geht, ist es weitaus einfacher, einen vollständigen Tabellenscan durchzuführen als bei einigen anderenfilesort
als einen vollständigen Indexscan und eine Zeilensuche über den gen_clust_index für jeden sekundären Indexeintrag.
Ich bin kein Befürworter der Verwendung von Indexhinweisen, da der EXPLAIN-Plan ignoriert wird. Wenn Sie Ihre Daten jedoch wirklich besser kennen als InnoDB, müssen Sie auf Indexhinweise zurückgreifen, insbesondere bei Abfragen ohne WHERE
Klausel.
UPDATE 2012-11-14 14:21 EDT
Nach dem Buch MySQL-Interna verstehen
Seite 202 Absatz 7 besagt Folgendes:
Die Daten werden in einer speziellen Struktur gespeichert , die als Clustered-Index bezeichnet wird. Dabei handelt es sich um einen B-Baum mit dem Primärschlüssel als Schlüsselwert und dem tatsächlichen Datensatz (anstelle eines Zeigers) im Datenteil. Daher muss jede InnoDB-Tabelle einen Primärschlüssel haben. Wenn keine angegeben ist, wird eine spezielle Zeilen-ID-Spalte hinzugefügt, die für den Benutzer normalerweise nicht sichtbar ist, und dient als Primärschlüssel. Ein Sekundärschlüssel speichert den Wert des Primärschlüssels, der den Datensatz identifiziert. Der B-Tree-Code befindet sich in innobase / btr / btr0btr.c .
Aus diesem Grund habe ich bereits ausgeführt: Es ist viel einfacher, einen vollständigen Tabellenscan und dann einen Dateisort durchzuführen, als für jeden sekundären Indexeintrag einen vollständigen Indexscan und eine Zeilensuche über den gen_clust_index durchzuführen . InnoDB wird jedes Mal einen Doppelindex-Lookup durchführen . Das klingt brutal, aber das sind nur die Fakten. Berücksichtigen Sie auch hier die fehlende WHERE
Klausel. Dies ist an sich der Hinweis für den MySQL Query Optimizer, einen vollständigen Tabellenscan durchzuführen.
FOR ORDER BY
(was in dieser Frage der Sonderfall ist). Die Frage ergab, dass in diesem Fall die Speicher-Engine warInnoDB
(und die ursprüngliche SO-Frage zeigt, dass die 10-KB-Zeilen ziemlich gleichmäßig auf 8 Elemente verteilt sind, Kardinalität sollte auch hier kein Problem sein). Leider glaube ich nicht, dass dies die Frage beantwortet.