2017-09-26 4 views
3

Es ist eine typische Users Tabelle, die Felder enthält: id (primary), application_id, login, phone usw. (application_id - selektives Feld)MySQL verwendet falschen Index. Warum?

Es gibt nur wenige Indizes sind:

index_users_on_application_id, 
unique_index_users_on_application_id_and_login 
unique_index_users_on_application_id_and_phone 

Die Abfrage selbst ist sehr einfach:

SELECT `users`.* 
FROM `users` 
WHERE `users`.`application_id` = 1234 
LIMIT 10 OFFSET 0; 

Der schwierige Teil ist, dass diese Abfrage einen von zwei eindeutigen Indizes verwendet (unique_index_users_on_application_id_and_login zum Beispiel) und gibt dann eine Liste der Benutzer zurück, sortiert nach login. Aber ich brauche sie sortiert nach id.

Zu diesem Zweck habe ich die Abfrage aktualisiert:

SELECT `users`.* 
FROM `users` 
WHERE `users`.`application_id` = 1234 
ORDER BY id 
LIMIT 10 OFFSET 0; 

Nun, jetzt zeigt erklären, dass MySQL PRIMARY Schlüssel anstelle von irgendwelchen Indizes beginnt mit. Aber wie ist das passiert? Wenn index_users_on_application_id tatsächlich zwei Felder enthalten sollte: [application_id, id] (InnoDB), so dass der Index für die Abfrage perfekt ist, aber MySQL beschließt, einen anderen zu wählen.

Wenn ich sage IGNORE INDEX(PRIMARY), beginnt MySQL mit unique_index_users_on_application_id_and_login, immer noch den richtigen Index zu ignorieren. Gleiches Ergebnis, wenn ORDER BY id+0.

Ich habe auch versucht, ORDER BY application_id, id um sicherzustellen, dass der Index am besten passt, wählt MySQL immer noch falsche Index.

Irgendwelche Ideen, warum passiert es und wie kann ich sicherstellen, dass MySQL den richtigen Index verwendet, ohne explizit USE INDEX(index_users_on_application_id) zu sagen?

Liste von Indizes für Users Tabelle:

mysql> show indexes from users; 
+-------+------------+-----------------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 
| Table | Non_unique | Key_name           | Seq_in_index | Column_name   | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | 
+-------+------------+-----------------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 
| users |   0 | PRIMARY            |   1 | id     | A   |  21893 |  NULL | NULL |  | BTREE  |   |    | 
| users |   0 | index_users_on_confirmation_token     |   1 | confirmation_token | A   |   28 |  NULL | NULL | YES | BTREE  |   |    | 
| users |   0 | index_users_on_reset_password_token     |   1 | reset_password_token | A   |   50 |  NULL | NULL | YES | BTREE  |   |    | 
| users |   0 | index_users_on_application_id_and_external_user_id |   1 | application_id  | A   |   32 |  NULL | NULL | YES | BTREE  |   |    | 
| users |   0 | index_users_on_application_id_and_external_user_id |   2 | external_user_id  | A   |   995 |  NULL | NULL | YES | BTREE  |   |    | 
| users |   0 | index_users_on_application_id_and_login    |   1 | application_id  | A   |   30 |  NULL | NULL | YES | BTREE  |   |    | 
| users |   0 | index_users_on_application_id_and_login    |   2 | login    | A   |  21893 |  NULL | NULL | YES | BTREE  |   |    | 
| users |   1 | users_account_id_fk         |   1 | account_id   | A   |   44 |  NULL | NULL |  | BTREE  |   |    | 
| users |   1 | users_blob_id_fk         |   1 | blob_id    | A   |   118 |  NULL | NULL | YES | BTREE  |   |    | 
| users |   1 | index_users_on_remember_token      |   1 | remember_token  | A   |   8 |  NULL | NULL | YES | BTREE  |   |    | 
| users |   1 | index_users_on_application_id      |   1 | application_id  | A   |   32 |  NULL | NULL | YES | BTREE  |   |    | 
| users |   1 | index_users_on_application_id_and_facebook_id  |   1 | application_id  | A   |   32 |  NULL | NULL | YES | BTREE  |   |    | 
| users |   1 | index_users_on_application_id_and_facebook_id  |   2 | facebook_id   | A   |  3127 |  NULL | NULL | YES | BTREE  |   |    | 
| users |   1 | index_users_on_application_id_and_twitter_digits_id |   1 | application_id  | A   |   32 |  NULL | NULL | YES | BTREE  |   |    | 
| users |   1 | index_users_on_application_id_and_twitter_digits_id |   2 | twitter_digits_id | A   |   138 |  NULL | NULL | YES | BTREE  |   |    | 
| users |   1 | index_users_on_application_id_and_email    |   1 | application_id  | A   |   32 |  NULL | NULL | YES | BTREE  |   |    | 
| users |   1 | index_users_on_application_id_and_email    |   2 | email    | A   |  2189 |  NULL | NULL | YES | BTREE  |   |    | 
| users |   1 | index_users_on_application_id_and_full_name   |   1 | application_id  | A   |   32 |  NULL | NULL | YES | BTREE  |   |    | 
| users |   1 | index_users_on_application_id_and_full_name   |   2 | full_name   | A   |  5473 |  NULL | NULL | YES | BTREE  |   |    | 
+-------+------------+-----------------------------------------------------+--------------+----------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+ 
19 rows in set (0.01 sec) 

Beispiel ERKLäREN:

mysql> EXPLAIN SELECT `users`.* FROM `users` WHERE `users`.`application_id` = 56374 ORDER BY id asc LIMIT 1 OFFSET 0; 
+----+-------------+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------+---------+-------+------+-----------------------------+ 
| id | select_type | table | type | possible_keys                                                                         | key            | key_len | ref | rows | Extra      | 
+----+-------------+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------+---------+-------+------+-----------------------------+ 
| 1 | SIMPLE  | users | ref | index_users_on_application_id_and_external_user_id,index_users_on_application_id_and_login,index_users_on_application_id,index_users_on_application_id_and_facebook_id,index_users_on_application_id_and_twitter_digits_id,index_users_on_application_id_and_email,index_users_on_application_id_and_full_name | index_users_on_application_id_and_external_user_id | 5  | const | 1 | Using where; Using filesort | 
+----+-------------+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------+---------+-------+------+-----------------------------+ 
1 row in set (0.00 sec) 

Das Problem selbst ist, dass falscher Index mit verursacht Abfragen wie die (mit einem Limit von 100 statt 1) MINUTEN durchgeführt werden, während es bei einem korrekten Index um Sekundenbruchteile geht.

Profilierung:

SET PROFILING = 1; SELECT `users`.* FROM `users` WHERE `users`.`application_id` = 56374 ORDER BY id asc LIMIT 1 OFFSET 0; SHOW PROFILE FOR QUERY 1; SET PROFILING = 0; 
Query OK, 0 rows affected (0.00 sec) 

+----------+----------+-----------------+-------+-------+----------------------------------------------------------------------------------------------------------------------------------+----------------------+-------+---------+---------------------+---------------------+---------------------+------------------+-------------+------------+---------+----------------------+------------------------+---------------------+--------------------+---------------------+----------------------+-------------------+------------+----------------+-------------+---------------------------------------------------------------------------------------+-------------------+-------------------------+--------------------------------------------------+----------------+ 
-- fields list -- 
+----------+----------+-----------------+-------+-------+----------------------------------------------------------------------------------------------------------------------------------+----------------------+-------+---------+---------------------+---------------------+---------------------+------------------+-------------+------------+---------+----------------------+------------------------+---------------------+--------------------+---------------------+----------------------+-------------------+------------+----------------+-------------+---------------------------------------------------------------------------------------+-------------------+-------------------------+--------------------------------------------------+----------------+ 
| 27265241 |  NULL | Some Username | NULL | 9777 | SomeHash | AnotherHash | NULL | NULL | 2017-04-12 15:53:32 | 2017-09-21 13:39:51 | 2017-09-24 19:19:06 |    1234 | NULL  | NULL  | NULL | NULL     | NULL     | NULL    | NULL    | 2017-07-05 10:59:59 | NULL     | NULL    |  12345 | NULL   | NULL  | something_else | NULL    |      1 | another_hash |   54321 | 
+----------+----------+-----------------+-------+-------+----------------------------------------------------------------------------------------------------------------------------------+----------------------+-------+---------+---------------------+---------------------+---------------------+------------------+-------------+------------+---------+----------------------+------------------------+---------------------+--------------------+---------------------+----------------------+-------------------+------------+----------------+-------------+---------------------------------------------------------------------------------------+-------------------+-------------------------+--------------------------------------------------+----------------+ 
1 row in set (1 min 14.43 sec) 

+--------------------------------+-----------+ 
| Status       | Duration | 
+--------------------------------+-----------+ 
| starting      | 0.000068 | 
| Waiting for query cache lock | 0.000025 | 
| init       | 0.000025 | 
| checking query cache for query | 0.000047 | 
| checking permissions   | 0.000026 | 
| Opening tables     | 0.000031 | 
| After opening tables   | 0.000025 | 
| System lock     | 0.000025 | 
| Table lock      | 0.000026 | 
| Waiting for query cache lock | 0.000037 | 
| init       | 0.000046 | 
| optimizing      | 0.000032 | 
| statistics      | 0.000225 | 
| preparing      | 0.000042 | 
| executing      | 0.000025 | 
| Sorting result     | 0.000057 | 
| Sending data     | 42.952100 | 
| end       | 0.000070 | 
| query end      | 0.000027 | 
| closing tables     | 0.000025 | 
| Unlocking tables    | 0.000028 | 
| freeing items     | 0.000028 | 
| updating status    | 0.000039 | 
| cleaning up     | 0.000025 | 
+--------------------------------+-----------+ 
24 rows in set (0.00 sec) 

Query OK, 0 rows affected (0.00 sec) 
+0

Es ist sehr schwer, Ihre Frage zu folgen - können Sie bitte die volle DDL für diese Tabelle veröffentlichen, einschließlich Indizes und den Abfrageplan zeigen? –

+0

Bitte veröffentlichen Sie das Ergebnis von 'show Indizes von Benutzern;' – fancyPants

+1

hinzugefügt beide Liste von Indizes und erklären Beispiel – nattfodd

Antwort

0

Sie sollten korrekte Index vorschlagen Nutzungsindex-Hinweise und Optimierer verwenden Hinweise in der Lage:

Index hints

Optimizer hints

Sie könnten direkt andeuten die Tabelle:

tbl_name [[AS] alias] [index_hint_list] 

index_hint_list: 
    index_hint [index_hint] ... 

index_hint: 
    USE {INDEX|KEY} 
     [FOR {JOIN|ORDER BY|GROUP BY}] ([index_list]) 
    | IGNORE {INDEX|KEY} 
     [FOR {JOIN|ORDER BY|GROUP BY}] (index_list) 
    | FORCE {INDEX|KEY} 
     [FOR {JOIN|ORDER BY|GROUP BY}] (index_list) 

index_list: 
    index_name [, index_name] ... 

In Ihrem Fall denke ich, die beste Lösung, um die Attribute nach Namen und nicht zu verwenden Stern * und zu verwenden IGNORE INDEX (unique_index_users_on_application_id_and_login, unique_index_users_on_application_id_and_phone) FOR ORDER BY direkt bei der Abfrage zu nennen wäre:

Ein Beispiel auf dem Code basiert:

SELECT u.id, 
     u.application_id, 
     u.login, 
     u.phone, 
     # ... here to continue 
FROM users as u 
IGNORE INDEX (unique_index_users_on_application_id_and_login, unique_index_users_on_application_id_and_phone) FOR ORDER BY 
WHERE u.application_id = 1234 
ORDER BY u.id 
LIMIT 10 OFFSET 0; 

Erstes Editieren
Aufgrund eines Kommentars unten füge ich einen Trick mit der Ungültigmachung eines Primärschlüssels hinzu.

Sie auch eine Möglichkeit für unwirksam zu erklären den Primärschlüssel von diesem Trick gehen könnte:

SELECT u.id, 
     u.application_id, 
     u.login, 
     u.phone, 
     #... 
FROM users as u 
WHERE u.application_id = 1234 
ORDER BY u.id+0 
+1

Sollte NICHT INDEX oder sogar FORCE INDEX (correct_index) in diesem Fall effektiver sein, da Sie die Indizes bereits angeben? –

+0

@MilanVelebit Ja Use & Force wäre effektiver. Ich halte mich an den 'index ', ohne explizit USE INDEX (index_users_on_application_id) zu sagen. – tukan

+0

Wenn Sie die Frage beantwortet haben, können Sie mir bitte erklären, was das eigentliche Problem ist und was Ihre Antwort für das OP löst? – Mjh

0

Es ist schwer, sicher zu sein, aber in sehr vielen Fällen ist MySQL gut darin, den richtigen Index auswählen.

Es kann notwendig sein, update the statistics für den Abfrage-Analysator - gelegentlich hat der Abfrage-Analysator keine guten Informationen über die Daten, und dies kann zu seltsamen Verhalten führen.

Jedoch ... Ihre index_users_on_application_id hat nur application_id, aber nicht ID.

Ich vermute, dass application_id eine ziemlich geringe Anzahl von verschiedenen Werten hat. Die Spalte ID ist eindeutig und enthält so viele Werte wie Zeilen in der Tabelle. Es gibt keinen Index, der sowohl die where-Klausel als auch die order by-Klausel enthält. Daher schätzt MySQL, dass der teuerste Teil von ID sortiert wird, statt nach application_id zu filtern.

Also, ich denke, MySQL macht hier das Richtige. Hinter den Kulissen gibt es alle nach ID sortierten Zeilen zurück und durchläuft diese Liste und gibt Ihnen die ersten 10 mit den angegebenen application_ID.

Die offensichtliche Sache ist es, einen Index mit application_id und ID zu erstellen und zu sehen, ob MySQL das wählt.

+0

Also schlagen Sie vor, einen weiteren Index für application_id + id hinzuzufügen, und es wird funktionieren? Ich dachte, dass die Indexverschmelzung dasselbe unter der Haube tut –

+0

Meine Vermutung ist, dass MySQL entschieden hat, dass es nicht genug unterschiedliche Werte in application_id gibt, um den Index merge lohnend zu machen. –

+1

> Es ist naheliegend, einen Index mit application_id und ID zu erstellen und zu sehen, ob MySQL das wählt. Ich tat das auch, es tut es nicht. Das ist offensichtlich, da 'index_users_on_application_id' dasselbe ist (es enthält' id' durch Definition des sekundären Index) – nattfodd

-1

Wenn Sie ORDER BY column_name verwenden MySQL alle Daten in Spalte in ORDER BY-Klausel gescannt, hier in Ihrem Fall id Spalte wird Scan bekommen. Verwenden Sie für diese Datenbank Tabellenzeiger. In InnoDB ist es der Wert von PRIMARY KEY und in MyISAM ist es ein Offset in der .MYD-Datei.

Aus diesem Grund verwenden Sie bei Verwendung der ORDER BY-ID nur Primärschlüssel als Index.

Um Sie verwenden erstellt Index hinzufügen id Spalte als erste Spalte des Index. Dann wird der Index effektiv verwendet.Ihr Index unique_index_users_on_application_id_and_login on users table sollte Spalten wie folgt in der gleichen Reihenfolge enthalten: 1- id & 2- application_id.

Für weitere Informationen über die Performance von MySQL von ORDER BY/LIMIT Go here

+0

Ich denke, du liegst falsch bei mehreren Problemen. 'ORDER BY id' kann den PK _preferred_, aber nicht erforderlich machen. Das Hinzufügen von 'id' am Anfang eines Index macht es nicht besser als das PK. Wenn Sie über '=' filtern und nach etwas anderem sortieren, muss die Filterspalte _must_ zuerst im Index stehen. –

+0

Ich weiß, es ist nicht besser, "id" am Anfang eines Indexes hinzuzufügen, aber @nattfodd fragt ausdrücklich, wie man die Indizierung effektiv einsetzt, ohne explizit USE INDEX (index_users_on_application_id) zu sagen. Dies kann nur erreicht werden, indem 'id' am Anfang eines Indexes hinzugefügt wird. –

Verwandte Themen