2017-08-31 89 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%的表时,通常会发生“不使用索引”。

相关问题