为什么 MySql 不使用我们 table 上的索引?
Why isn't MySql using the index on our table?
我们正在使用 MySql 5.5.37、Java 7 和 Hibernate 5.1.3。 Hibernate 是自动生成查询的,有一个让我感到困惑。我们有这些 tables ...
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 |
然后我们通过 join table
将它们连接在一起
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 |
+----+-------------+-----------+--------+--------------------------------------+------------------+---------+---------------------------+---------+--------------------------+
除了通过主键的连接之外,您没有可用于搜索条件的索引,除了术语 role2_.ID in ('Student'))
。所以 MySQL 接受那个词,从 table roles1_
(table user_roles
) 开始(通过连接关系 role2_.id = roles1_.role_id
)寻找所有学生使用索引 FK2_USER_ROLE
(并估计在 table 中获得大约 160 万行),然后通过主键加入其他 table。
搜索
where user0_.url='mycityst.myco.org'
and (role2_.ID in ('Student'))
and lower(address3_.email)='myemail@gmail.com'
and user0_.ENABLED=1;
您缺少 user0_.url
上的可用键(您在 table 中只有 (USER_NAME,URL)
上的索引),并且 address3_.email
上的潜在索引不能因为 lower()
-函数而被使用。 (虽然您的地址描述table 丢失了,所以不清楚您是否有索引)。
因此,作为快速修复,为 user0_(url)
添加索引(可能包括 enabled
)。
您还应该重新考虑 utf8_bin
的使用。它认为 'A' 不同于 'a'。因此,如果您真的想进行不区分大小写的搜索,它会阻止您使用索引,而这通常是针对电子邮件、姓名、地址或 url 进行的。使用像 lower(email)
这样的函数也可以防止在该列上使用索引。因此,如果可能的话,用不区分大小写的排序规则替换您的列(例如 utf8_unicode_ci
,ci
代表 case insensitive
)。
其他问题:
由于 role
的 ID 和名称似乎都最多 40 个字符,我建议去掉 role
table 并替换 ROLE_ID
ROLE_NAME
无论出现在哪里。
然后在user_role
中提升UNIQUE KEY
到PRIMARY KEY
。
我没有看到 address
table,但我怀疑它的排序规则是 utf8_bin
?相反,如果它是 utf8_unique_ci
,那么您可以将其编入索引并更改
and lower(address3_.email) = 'myemail@gmail.com'
到
and address3_.email = 'myemail@gmail.com'
从而为优化器提供更好的查询启动方式。
关于
的问题
MySql seems to be ignoring the index. Notice the "1688568" rows in the explain plan.
正在使用索引;但是 table.
中大约有 "1688568" "Students"
你真正的问题是"Why did it start with role
instead of some better table?"
MySQL将单项IN
优化为=
,将多项OR
优化为IN
。 但是,优化器可能仍然选择其他索引,或者不选择索引。 "Not using an index" 通常发生在索引值引用超过 ~20% 的 table 时。
我们正在使用 MySql 5.5.37、Java 7 和 Hibernate 5.1.3。 Hibernate 是自动生成查询的,有一个让我感到困惑。我们有这些 tables ...
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 |
然后我们通过 join table
将它们连接在一起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 |
+----+-------------+-----------+--------+--------------------------------------+------------------+---------+---------------------------+---------+--------------------------+
除了通过主键的连接之外,您没有可用于搜索条件的索引,除了术语 role2_.ID in ('Student'))
。所以 MySQL 接受那个词,从 table roles1_
(table user_roles
) 开始(通过连接关系 role2_.id = roles1_.role_id
)寻找所有学生使用索引 FK2_USER_ROLE
(并估计在 table 中获得大约 160 万行),然后通过主键加入其他 table。
搜索
where user0_.url='mycityst.myco.org'
and (role2_.ID in ('Student'))
and lower(address3_.email)='myemail@gmail.com'
and user0_.ENABLED=1;
您缺少 user0_.url
上的可用键(您在 table 中只有 (USER_NAME,URL)
上的索引),并且 address3_.email
上的潜在索引不能因为 lower()
-函数而被使用。 (虽然您的地址描述table 丢失了,所以不清楚您是否有索引)。
因此,作为快速修复,为 user0_(url)
添加索引(可能包括 enabled
)。
您还应该重新考虑 utf8_bin
的使用。它认为 'A' 不同于 'a'。因此,如果您真的想进行不区分大小写的搜索,它会阻止您使用索引,而这通常是针对电子邮件、姓名、地址或 url 进行的。使用像 lower(email)
这样的函数也可以防止在该列上使用索引。因此,如果可能的话,用不区分大小写的排序规则替换您的列(例如 utf8_unicode_ci
,ci
代表 case insensitive
)。
其他问题:
由于 role
的 ID 和名称似乎都最多 40 个字符,我建议去掉 role
table 并替换 ROLE_ID
ROLE_NAME
无论出现在哪里。
然后在user_role
中提升UNIQUE KEY
到PRIMARY KEY
。
我没有看到 address
table,但我怀疑它的排序规则是 utf8_bin
?相反,如果它是 utf8_unique_ci
,那么您可以将其编入索引并更改
and lower(address3_.email) = 'myemail@gmail.com'
到
and address3_.email = 'myemail@gmail.com'
从而为优化器提供更好的查询启动方式。
关于
的问题MySql seems to be ignoring the index. Notice the "1688568" rows in the explain plan.
正在使用索引;但是 table.
中大约有 "1688568" "Students"你真正的问题是"Why did it start with role
instead of some better table?"
MySQL将单项IN
优化为=
,将多项OR
优化为IN
。 但是,优化器可能仍然选择其他索引,或者不选择索引。 "Not using an index" 通常发生在索引值引用超过 ~20% 的 table 时。