为什么不是';t MySql使用表上的索引吗?

为什么不是';t MySql使用表上的索引吗?,mysql,sql,hibernate,indexing,sql-execution-plan,Mysql,Sql,Hibernate,Indexing,Sql Execution Plan,我们使用的是MySql 5.5.37、Java 7和Hibernate 5.1.3。Hibernate是自动生成查询的,有一个让我很困惑。我们有这些桌子 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 DEFAU

我们使用的是MySql 5.5.37、Java 7和Hibernate 5.1.3。Hibernate是自动生成查询的,有一个让我很困惑。我们有这些桌子

 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 |

我们用连接表将它们连接在一起

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) = 'myemail@gmail.com'
      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              |
+----+-------------+-----------+--------+--------------------------------------+------------------+---------+---------------------------+---------+--------------------------+

除了通过主键的连接外,除了('Student')中的术语
role2.ID)之外,没有可用于搜索条件的索引。因此,MySQL使用这个术语,从表
roles1\uuu
(表
user\u roles
)(通过连接关系
role2\uid=roles1\uu.role\u id
)开始查找所有使用索引
FK2\u user\u role
(估计该表中有160万行),然后通过主键连接其他表

搜寻

where user0_.url='mycityst.myco.org' 
and (role2_.ID in ('Student')) 
and lower(address3_.email)='myemail@gmail.com' 
and user0_.ENABLED=1;
您在
user0.url
上缺少可用键(该表中只有
(用户名,url)
上的索引),而
address3上的潜在索引。由于
lower()
-函数,无法使用电子邮件。(但是缺少对addresstable的描述,因此不清楚您是否有索引)

因此,作为一个快速修复,为
user0(url)
添加一个索引(可能包括
enabled


您还应该重新考虑使用
utf8\u bin
。它认为“A”不同于“A”。因此,如果您确实想要进行不区分大小写的搜索,它会阻止您使用索引,而您通常希望对电子邮件、名称、地址或URL进行搜索。使用诸如
lower(email)
之类的函数也可以防止在该列上使用索引。因此,如果可能,请使用不区分大小写的排序规则替换列(例如,
utf8\u unicode\u ci
ci
代表
不区分大小写的

除了通过主键的连接之外,除了('Student')中的术语
role2.ID之外,没有可用于搜索条件的索引。
。因此,MySQL使用这个术语,从表
roles1\uuu
(表
user\u roles
)(通过连接关系
role2\uid=roles1\uu.role\u id
)开始查找所有使用索引
FK2\u user\u role
(估计该表中有160万行),然后通过主键连接其他表

搜寻

where user0_.url='mycityst.myco.org' 
and (role2_.ID in ('Student')) 
and lower(address3_.email)='myemail@gmail.com' 
and user0_.ENABLED=1;
您在
user0.url
上缺少可用键(该表中只有
(用户名,url)
上的索引),而
address3上的潜在索引。由于
lower()
-函数,无法使用电子邮件。(但是缺少对addresstable的描述,因此不清楚您是否有索引)

因此,作为一个快速修复,为
user0(url)
添加一个索引(可能包括
enabled

您还应该重新考虑使用
utf8\u bin
。它认为“A”不同于“A”。因此,如果您确实想要进行不区分大小写的搜索,它会阻止您使用索引,而您通常希望对电子邮件、名称、地址或URL进行搜索。使用诸如
lower(email)
之类的函数也可以防止在该列上使用索引。因此,如果可能,请使用不区分大小写的排序规则替换列(例如,
utf8\u unicode\u ci
ci
代表
不区分大小写的

其他问题:

由于
角色
的ID和名称似乎都最多有40个字符,因此我建议去掉
角色
表,并将
角色ID
替换为
角色名

然后将
用户角色
中的
唯一键提升为
主键

我没有看到
地址
表,但我怀疑它的排序规则是
utf8\u bin
?相反,如果它是
utf8\u unique\u ci
,那么您可以为它编制索引并进行更改

and  lower(address3_.email) = 'myemail@gmail.com'

从而为优化器提供了更好的启动查询的方法

至于关于

MySql似乎忽略了索引。请注意解释计划中的“1688568”行

它正在使用索引;但表中有大约1688568名“学生”

你真正的问题是“为什么它是从
角色开始的,而不是一些更好的表格?”

MySQL将
中的单个项目
优化为
=
,并将多个项目
优化为
中的
。但是,优化器仍然可以选择其他索引,或者不选择索引。“不使用索引”通常发生在索引值引用表的20%以上时。

其他问题:

由于
角色
的ID和名称似乎都最多有40个字符,因此我建议去掉
角色
表,并将
角色ID
替换为
角色名

然后将
用户角色
中的
唯一键提升为
主键

我没有看到
地址
表,但我怀疑它的排序规则是
utf8\u bin
?相反,如果它是
utf8\u unique\u ci
,那么您可以为它编制索引并进行更改

and  lower(address3_.email) = 'myemail@gmail.com'

从而为优化器提供了更好的启动查询的方法

至于关于

MySql似乎忽略了索引。请注意解释计划中的“1688568”行

它正在使用索引;但表中有大约1688568名“学生”

你真正的问题是“为什么它是从
角色开始的,而不是一些更好的表格?”

MySQL将
中的单个项目
优化为
=
,并将多个项目
优化为
中的
。但是,优化器可能仍然选择其他一些索引,或者不选择i