2017-08-31 112 views
1

我們使用MySql 5.5.37,Java 7和Hibernate 5.1.3。 Hibernate是自動生成查詢,有一個讓我感到困惑。我們有這些表...爲什麼MySql沒有在我們的表上使用索引?

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 | 

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 | 

,我們有一個連接表將它們連接在一起

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 | 

但是,當我們查詢一個用戶,包括在角色我們的查詢MySql似乎忽略了索引。請注意解釋計劃中的「1688568」行。爲什麼MySQL不使用索引以及哪種方式更好地查詢數據?

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              | 
+----+-------------+-----------+--------+--------------------------------------+------------------+---------+---------------------------+---------+--------------------------+ 

回答

3

除了通過主鍵聯接,你有沒有索引是除了長期role2_.ID in ('Student'))你的搜索條件下使用。因此,MySQL從這個詞開始,從表roles1_(表user_roles)(通過連接關係role2_.id = roles1_.role_id)尋找所有使用索引FK2_USER_ROLE(並估計在該表中獲得約160萬行)的學生,然後加入其他表通過主鍵。

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

你缺少對user0_.url可用鍵搜索(你只有在該表上(USER_NAME,URL)索引),並在address3_.email一個潛在的索引無法使用,因爲lower()的 - 功能。 (雖然你的addresstable的描述丟失了,所以目前還不清楚你是否有索引)。

所以,作爲一個快速修復,添加索引user0_(url)(也許包括enabled)。

您還應該重新考慮使用utf8_bin。它認爲'A'與'a'不同。因此,如果您確實想要執行不區分大小寫的搜索,您通常會想要對電子郵件,姓名,地址或網址進行搜索,它會阻止您使用索引。使用像lower(email)這樣的函數也可以防止在該列上使用索引。因此,如果可能,請用不區分大小寫的排序規則替換列(例如utf8_unicode_cici代表case insensitive)。

1

其他問題:

由於ID和role的名字似乎都被最多40個字符,我建議擺脫role表,並與ROLE_NAME更換ROLE_ID不論在何處發生。

進而促進UNIQUE KEYPRIMARY KEYuser_role

我沒有看到address表,但我懷疑它的排序是utf8_bin?相反,如果它一直utf8_unique_ci,那麼你可以索引並改變

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

and address3_.email = '[email protected]' 

從而給優化有更好的方式開始查詢。

至於問題有關

的MySql似乎忽略了指數。請注意解釋計劃中的「1688568」行。

它使用的是索引;但桌子上有「1688568」「學生」。

你真正的問題是「爲什麼它以role開頭,而不是一些更好的表?

MySQL將單項IN優化爲=,並將多項OR優化爲IN但是,優化器可能仍然會選擇一些其他索引,或者沒有索引。當索引值引用超過20%的表時,通常會發生「不使用索引」。

相關問題