2017-08-31 1 views
1

Wir verwenden MySql 5.5.37, Java 7 und Hibernate 5.1.3. Hibernate generiert automatisch Abfragen und es gibt eine, die mich verwirrt. Wir haben diese Tabellen ...Warum verwendet MySql nicht den Index auf unserem Tisch?

CREATE TABLE `user` (
    `ID` varchar(32) COLLATE utf8_bin NOT NULL, 
    `FIRST_NAME` varchar(50) COLLATE utf8_bin NOT NULL, 
    `MIDDLE_NAME` varchar(50) COLLATE utf8_bin DEFAULT NULL, 
    `LAST_NAME` varchar(50) COLLATE utf8_bin NOT NULL, 
    `USER_NAME` varchar(50) COLLATE utf8_bin NOT NULL, 
    `URL` varchar(200) COLLATE utf8_bin NOT NULL, 
    `SALUTATION` varchar(10) COLLATE utf8_bin DEFAULT NULL, 
    ... 
    `ADDRESS_ID` varchar(32) COLLATE utf8_bin DEFAULT NULL, 
    PRIMARY KEY (`ID`), 
    UNIQUE KEY `USER_IDX_01` (`USER_NAME`,`URL`), 
    KEY `FK2_USER` (`GRADE_ID`), 
    KEY `FK4_USER` (`USER_DEMOGRAPHIC_INFO_ID`), 
    KEY `FK3_USER` (`CREATOR_ID`), 
    KEY `FK_USER` (`ADDRESS_ID`), 
    CONSTRAINT `FK2_USER` FOREIGN KEY (`GRADE_ID`) REFERENCES `grade` (`ID`), 
    CONSTRAINT `FK3_USER` FOREIGN KEY (`CREATOR_ID`) REFERENCES `user` (`ID`) ON UPDATE NO ACTION, 
    CONSTRAINT `FK_USER` FOREIGN KEY (`ADDRESS_ID`) REFERENCES `cb_address` (`ID`) ON UPDATE NO ACTION 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin | 

und

role | CREATE TABLE `role` (
    `ID` varchar(40) COLLATE utf8_bin NOT NULL, 
    `NAME` varchar(40) COLLATE utf8_bin NOT NULL, 
    PRIMARY KEY (`ID`), 
    UNIQUE KEY `ROLE_IDX_01` (`NAME`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin | 

und wir verbinden sie zusammen mit einer Join-Tabelle

user_role | CREATE TABLE `user_role` (
    `USER_ID` varchar(32) COLLATE utf8_bin NOT NULL, 
    `ROLE_ID` varchar(40) COLLATE utf8_bin NOT NULL, 
    UNIQUE KEY `USER_ROLE_IDX_02` (`USER_ID`,`ROLE_ID`), 
    KEY `FK2_USER_ROLE` (`ROLE_ID`), 
    CONSTRAINT `FK1_USER_ROLE` FOREIGN KEY (`USER_ID`) REFERENCES `user` (`ID`) ON DELETE CASCADE ON UPDATE NO ACTION, 
    CONSTRAINT `FK2_USER_ROLE` FOREIGN KEY (`ROLE_ID`) REFERENCES `role` (`ID`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin | 

Aber wenn wir Abfrage für einen Benutzer und sind die Rollen in In unserer Anfrage scheint MySql den Index zu ignorieren. Beachten Sie die Zeilen "1688568" im EXPLAIN-Plan. Warum verwendet MySQL den Index nicht und was wäre eine bessere Möglichkeit, die Daten abzufragen?

mysql> EXPLAIN  select user0_.id as id1_112_, user0_.ADDRESS_ID as ADDRESS17_112_, 
     user0_.AVATAR as AVATAR2_112_, user0_.CREATED_ON as CREATED_3_112_, 
     user0_.CREATOR_ID as CREATOR18_112_, user0_.DOB as DOB4_112_, 
     user0_.ENABLED as ENABLED5_112_, user0_.EXPIRATION as EXPIRATI6_112_, 
     user0_.first_name as first_na7_112_, user0_.GRADE_ID as GRADE_I19_112_, 
     user0_.INCORRECT_LOGINS as INCORREC8_112_, user0_.last_name as last_nam9_112_, 
     user0_.middle_name as middle_10_112_, user0_.password as passwor11_112_, 
     user0_.RESET_STATE as RESET_S12_112_, user0_.salutation as salutat13_112_, 
     user0_.temporary_password as tempora14_112_, user0_.url as url15_112_, 
     user0_.USER_DEMOGRAPHIC_INFO_ID as USER_DE20_112_, user0_.user_name as user_na16_112_ 
    from user user0_ 
    inner join user_role roles1_ ON user0_.id = roles1_.USER_ID 
    inner join role role2_ ON roles1_.ROLE_ID = role2_.ID 
    inner join cb_address address3_ ON user0_.ADDRESS_ID = address3_.id 
    where user0_.url = 'mycityst.myco.org' 
     and (role2_.ID in ('Student')) 
     and lower(address3_.email) = '[email protected]' 
     and user0_.ENABLED = 1; 

+----+-------------+-----------+--------+--------------------------------------+------------------+---------+---------------------------+---------+--------------------------+ 
| id | select_type | table     | type   | possible_keys                        | key              | key_len | ref                       | rows    | Extra                    | 
+----+-------------+-----------+--------+--------------------------------------+------------------+---------+---------------------------+---------+--------------------------+ 
|  1 | SIMPLE      | role2_    | const  | PRIMARY                              | PRIMARY          | 122     | const                     |       1 | Using index              | 
|  1 | SIMPLE      | roles1_   | ref    | USER_ROLE_IDX_02,FK2_USER_ROLE  | FK2_USER_ROLE | 122     | const                     | 1688568 | Using where; Using index | 
|  1 | SIMPLE      | user0_    | eq_ref | PRIMARY,FK_USER                   | PRIMARY          | 98      | schema1.roles1_.USER_ID   |       1 | Using where              | 
|  1 | SIMPLE      | address3_ | eq_ref | PRIMARY                              | PRIMARY          | 98      | schema1.user0_.ADDRESS_ID |       1 | Using where              | 
+----+-------------+-----------+--------+--------------------------------------+------------------+---------+---------------------------+---------+--------------------------+ 

Antwort

3

Neben dem über die Primärschlüssel verbinden, haben Sie keinen Index, der für Ihre Suchbedingung mit Ausnahme des Begriffs role2_.ID in ('Student')) verwendbar ist. Also nimmt MySQL diesen Begriff, beginnt an der Tabelle roles1_ (Tabelle user_roles) (über die Join-Relation role2_.id = roles1_.role_id) auf der Suche nach allen Studenten mit dem Index FK2_USER_ROLE (und Schätzung, um etwa 1,6 Millionen Zeilen in dieser Tabelle zu erhalten), dann tritt die anderen Tabellen über die Primärschlüssel.

Für die Suche nach

where user0_.url='mycityst.myco.org' 
and (role2_.ID in ('Student')) 
and lower(address3_.email)='[email protected]' 
and user0_.ENABLED=1; 

Sie einen verwendbaren Schlüssel auf user0_.url fehlt (nur einen Index in dieser Tabelle auf (USER_NAME,URL) haben) und ein Potential Index auf address3_.email kann nicht wegen der lower() verwendet werden - Funktion. (Die Beschreibung Ihres Adresses fehlt jedoch, daher ist es unklar, ob Sie überhaupt einen Index haben).

Also als eine schnelle Lösung, fügen Sie einen Index für user0_(url) (vielleicht einschließlich enabled).

Sie sollten auch die Verwendung von utf8_bin überdenken. Es sieht "A" anders als "a". Es verhindert also, dass Sie einen Index verwenden, wenn Sie tatsächlich eine Suche ohne Beachtung der Groß- und Kleinschreibung durchführen möchten, die Sie normalerweise für E-Mails, Namen, Adressen oder URLs durchführen möchten. Die Verwendung einer Funktion wie lower(email) verhindert die Verwendung eines Index für diese Spalte auch. Wenn möglich, ersetzen Sie Ihre Spalten durch eine Groß-/Kleinschreibung ohne Berücksichtigung der Groß- und Kleinschreibung (z. B. utf8_unicode_ci, ci steht für case insensitive).

1

Weitere Themen:

Da die ID und den Namen eines role scheinen beide zu 40 Zeichen werden, empfehle ich der role Tabelle loszuwerden und ersetzen ROLE_ID mit ROLE_NAME wo immer er auftritt.

Dann promote die UNIQUE KEY zu PRIMARY KEY in user_role.

Ich sehe nicht die address Tabelle, aber ich vermute, die Sortierung davon ist utf8_bin?Wenn stattdessen es utf8_unique_ci gewesen wäre, dann könnte man indizieren und ändern

and lower(address3_.email) = '[email protected]' 

zu

and address3_.email = '[email protected]' 

Damit der Optimierer einen besseren Weg geben, die Abfrage zu starten.

Was die Frage nach

MySql scheint den Index ignoriert werden. Beachten Sie die Zeilen "1688568" im EXPLAIN-Plan.

Es verwendet den Index; Aber es gibt ungefähr "1688568" "Studenten" in der Tabelle.

Ihre eigentliche Frage ist "Warum hat es mit role statt einer besseren Tabelle gestartet?"

MySQL optimiert Einzelartikel IN in = und Multi-Artikel OR in IN. Jedoch, der Optimierer kann immer noch einen anderen Index oder keinen Index auswählen. "Kein Index verwenden" tritt normalerweise auf, wenn mehr als 20% der Tabelle durch den indizierten Wert referenziert werden.

Verwandte Themen