Mysql 在使用索引条件自行加入 table 时不要使用索引
Mysql don't use index while self joining a table with indexed criteria
当自加入一个 table 并将条件作为索引列时,mysql 将进行完整的 table 扫描。通过更改连接顺序,我们可以使用索引。
Table EmployeeDetails 架构
EmployeeDetails | CREATE TABLE `EmployeeDetails` (
`CompanyId` bigint(19) NOT NULL DEFAULT '0',
`EmployeeId` bigint(19) NOT NULL DEFAULT '0',
`ParentEmployeeId` bigint(19) DEFAULT NULL,
PRIMARY KEY (`EmployeeId`),
KEY `EmployeeDetails_FK1_IDX` (`CompanyId`),
KEY `EmployeeDetails_FK2_IDX` (`ParentEmployeeId`),
CONSTRAINT `EmployeeDetails_FK1` FOREIGN KEY (`CompanyId`) REFERENCES `CompanyDetails` (`CompanyId`) ON DELETE CASCADE,
CONSTRAINT `EmployeeDetails_FK2` FOREIGN KEY (`ParentEmployeeId`) REFERENCES `EmployeeDetails` (`EmployeeId`) ON DELETE CASCADE,
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
Table CompanyDetails 架构
| CompanyDetails | CREATE TABLE `CompanyDetails` (
`CompanyId` bigint(19) NOT NULL DEFAULT '0',
`NAME` varchar(25) NOT NULL DEFAULT '',
`DESCRIPTION` varchar(255) DEFAULT NULL,
PRIMARY KEY (`CompanyId`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8
|
第一次查询 - 首先 select 将扫描 EmployeeDetails table 中的所有记录并进行自连接(不会使用标准中使用的索引 KEY EmployeeDetails_FK1_IDX (CompanyId))
mysql> select * from EmployeeDetails INNER JOIN `EmployeeDetails` `t1` ON `EmployeeDetails`.`EmployeeId`=`t1`.`ParentEmployeeId` where `EmployeeDetails`.`CompanyId` IN(123);
Empty set (0.10 sec)
mysql> explain select * from EmployeeDetails INNER JOIN `EmployeeDetails` `t1` ON `EmployeeDetails`.`EmployeeId`=`t1`.`ParentEmployeeId` where `EmployeeDetails`.`CompanyId` IN(123)\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t1
partitions: NULL
type: ALL
possible_keys: EmployeeDetails_FK2_IDX
key: NULL
key_len: NULL
ref: NULL
rows: 7999
filtered: 100.00
Extra: Using where
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: EmployeeDetails
partitions: NULL
type: eq_ref
possible_keys: PRIMARY,EmployeeDetails_FK1_IDX
key: PRIMARY
key_len: 8
ref: db.t1.ParentEmployeeId
rows: 1
filtered: 5.00
Extra: Using where
2 rows in set, 1 warning (0.01 sec)
第二个查询 - 第一个 select 使用 EmployeeDetails_FK1_IDX(CompanyId`) 索引的 EmployeeDetails 记录,仅扫描符合条件的记录并进行自连接
mysql> select * from EmployeeDetails INNER JOIN `EmployeeDetails` `t1` ON `t1`.`EmployeeId`=`EmployeeDetails`.`ParentEmployeeId` where `EmployeeDetails`.`CompanyId` IN(123);
Empty set (0.00 sec)
mysql> explain select * from EmployeeDetails INNER JOIN `EmployeeDetails` `t1` ON `t1`.`EmployeeId`=`EmployeeDetails`.`ParentEmployeeId` where `EmployeeDetails`.`CompanyId` IN(123)\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: EmployeeDetails
partitions: NULL
type: ref
possible_keys: EmployeeDetails_FK1_IDX,EmployeeDetails_FK2_IDX
key: EmployeeDetails_FK1_IDX
key_len: 8
ref: const
rows: 54
filtered: 100.00
Extra: Using where
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: t1
partitions: NULL
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: db.EmployeeDetails.ParentEmployeeId
rows: 1
filtered: 100.00
Extra: NULL
2 rows in set, 1 warning (0.00 sec)
为什么会这样?
从 EXPLAIN 中可以看出,"EmployeeDetails_FK2_IDX" 被列为可能的键。如果 MySQL 优化器不使用它,那只是它不会减少查询时间。
尝试用数据填充 table(我检查过它并且它按原样工作),您应该看到 MySQL 优化器将使用索引进行连接而不查看它们的订单。因为连接顺序对 MySQL 优化器算法没有任何影响。
同时勾选 STRAIGHT_JOIN
当自加入一个 table 并将条件作为索引列时,mysql 将进行完整的 table 扫描。通过更改连接顺序,我们可以使用索引。
Table EmployeeDetails 架构
EmployeeDetails | CREATE TABLE `EmployeeDetails` (
`CompanyId` bigint(19) NOT NULL DEFAULT '0',
`EmployeeId` bigint(19) NOT NULL DEFAULT '0',
`ParentEmployeeId` bigint(19) DEFAULT NULL,
PRIMARY KEY (`EmployeeId`),
KEY `EmployeeDetails_FK1_IDX` (`CompanyId`),
KEY `EmployeeDetails_FK2_IDX` (`ParentEmployeeId`),
CONSTRAINT `EmployeeDetails_FK1` FOREIGN KEY (`CompanyId`) REFERENCES `CompanyDetails` (`CompanyId`) ON DELETE CASCADE,
CONSTRAINT `EmployeeDetails_FK2` FOREIGN KEY (`ParentEmployeeId`) REFERENCES `EmployeeDetails` (`EmployeeId`) ON DELETE CASCADE,
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
Table CompanyDetails 架构
| CompanyDetails | CREATE TABLE `CompanyDetails` (
`CompanyId` bigint(19) NOT NULL DEFAULT '0',
`NAME` varchar(25) NOT NULL DEFAULT '',
`DESCRIPTION` varchar(255) DEFAULT NULL,
PRIMARY KEY (`CompanyId`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8
|
第一次查询 - 首先 select 将扫描 EmployeeDetails table 中的所有记录并进行自连接(不会使用标准中使用的索引 KEY EmployeeDetails_FK1_IDX (CompanyId))
mysql> select * from EmployeeDetails INNER JOIN `EmployeeDetails` `t1` ON `EmployeeDetails`.`EmployeeId`=`t1`.`ParentEmployeeId` where `EmployeeDetails`.`CompanyId` IN(123);
Empty set (0.10 sec)
mysql> explain select * from EmployeeDetails INNER JOIN `EmployeeDetails` `t1` ON `EmployeeDetails`.`EmployeeId`=`t1`.`ParentEmployeeId` where `EmployeeDetails`.`CompanyId` IN(123)\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t1
partitions: NULL
type: ALL
possible_keys: EmployeeDetails_FK2_IDX
key: NULL
key_len: NULL
ref: NULL
rows: 7999
filtered: 100.00
Extra: Using where
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: EmployeeDetails
partitions: NULL
type: eq_ref
possible_keys: PRIMARY,EmployeeDetails_FK1_IDX
key: PRIMARY
key_len: 8
ref: db.t1.ParentEmployeeId
rows: 1
filtered: 5.00
Extra: Using where
2 rows in set, 1 warning (0.01 sec)
第二个查询 - 第一个 select 使用 EmployeeDetails_FK1_IDX(CompanyId`) 索引的 EmployeeDetails 记录,仅扫描符合条件的记录并进行自连接
mysql> select * from EmployeeDetails INNER JOIN `EmployeeDetails` `t1` ON `t1`.`EmployeeId`=`EmployeeDetails`.`ParentEmployeeId` where `EmployeeDetails`.`CompanyId` IN(123);
Empty set (0.00 sec)
mysql> explain select * from EmployeeDetails INNER JOIN `EmployeeDetails` `t1` ON `t1`.`EmployeeId`=`EmployeeDetails`.`ParentEmployeeId` where `EmployeeDetails`.`CompanyId` IN(123)\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: EmployeeDetails
partitions: NULL
type: ref
possible_keys: EmployeeDetails_FK1_IDX,EmployeeDetails_FK2_IDX
key: EmployeeDetails_FK1_IDX
key_len: 8
ref: const
rows: 54
filtered: 100.00
Extra: Using where
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: t1
partitions: NULL
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 8
ref: db.EmployeeDetails.ParentEmployeeId
rows: 1
filtered: 100.00
Extra: NULL
2 rows in set, 1 warning (0.00 sec)
为什么会这样?
从 EXPLAIN 中可以看出,"EmployeeDetails_FK2_IDX" 被列为可能的键。如果 MySQL 优化器不使用它,那只是它不会减少查询时间。
尝试用数据填充 table(我检查过它并且它按原样工作),您应该看到 MySQL 优化器将使用索引进行连接而不查看它们的订单。因为连接顺序对 MySQL 优化器算法没有任何影响。
同时勾选 STRAIGHT_JOIN