MYSQL左连接在索引列上非常慢

时间:2016-11-04 04:22:29

标签: mysql sql

下面是4个表的表结构:

日历:

CREATE TABLE `calender` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `HospitalID` int(11) NOT NULL,
  `ColorCode` int(11) DEFAULT NULL,
  `RecurrID` int(11) NOT NULL,
  `IsActive` tinyint(1) NOT NULL DEFAULT '1',
  PRIMARY KEY (`ID`),
  UNIQUE KEY `ID_UNIQUE` (`ID`),
  KEY `idxHospital` (`ID`,`StaffID`,`HospitalID`,`ColorCode`,`RecurrID`,`IsActive`)
) ENGINE=InnoDB AUTO_INCREMENT=4638 DEFAULT CHARSET=latin1;

CalendarAttendee:

CREATE TABLE `calenderattendee` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `CalenderID` int(11) NOT NULL,
  `StaffID` int(11) NOT NULL,
  `IsActive` tinyint(1) NOT NULL DEFAULT '1',
  PRIMARY KEY (`ID`),
  KEY `idxCalStaffID` (`StaffID`,`CalenderID`)
) ENGINE=InnoDB AUTO_INCREMENT=20436 DEFAULT CHARSET=latin1;

CallPlanStaff:

CREATE TABLE `callplanstaff` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `Staffname` varchar(45) NOT NULL,
  `IsActive` tinyint(4) NOT NULL DEFAULT '1',
  PRIMARY KEY (`ID`),
  UNIQUE KEY `ID_UNIQUE` (`ID`),
  KEY `idx_IsActive` (`Staffname`,`IsActive`),
  KEY `idx_staffName` (`Staffname`,`ID`) USING BTREE KEY_BLOCK_SIZE=100
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=latin1;

用户:

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `email` varchar(255) NOT NULL DEFAULT '',
  `name` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `index_users_on_email` (`email`),
  UNIQUE KEY `index_users_on_name` (`name`),
  KEY `idx_email` (`email`) USING BTREE KEY_BLOCK_SIZE=100
) ENGINE=InnoDB AUTO_INCREMENT=33 DEFAULT CHARSET=utf8;

我要做的是使用以下查询获取calender.ID和Users.name:

SELECT a.ID, h.name
    FROM `stjude`.`calender` a 
    left join calenderattendee e on a.ID = e.calenderID
    left join callplanstaff f on e.StaffID = f.ID
    left join users h on f.Staffname = h.email

这些表之间的关系是:

MSDN

我花了大约4秒来获取13000条记录,我打赌它可能会更快。

当我查看查询的表格解释时,结果如下:

enter image description here

为什么MYSQL没有在callplanstaff表和users表上使用索引?

另外,在我的情况下,我应该使用多索引而不是多列索引吗?

是否有我缺少的索引,所以我的查询很慢?

=============================================== ========================

更新:

由于zedfoxus和spencer7593建议更改idxCalStaffID的排序和idx_staffname的排序,下面是执行计划:

enter image description here

获取需要0.063秒,所需的时间要少得多,索引的排序如何影响获取时间..?

3 个答案:

答案 0 :(得分:3)

您误解了EXPLAIN报告。

  • type: index不是一件好事。这意味着它正在做一个"索引扫描"它检查索引的每个元素。它几乎和桌面扫描一样糟糕。请注意列rows: 4562rows: 13451。这是它将为每个表检查的索引元素的估计数量。
  • 两个表执行索引扫描更糟糕。检查此连接的总行数为4562 x 13451 = 61,363,462。
  • Using join buffer不是一件好事。当优化器无法使用索引进行连接时,优化器就可以作为安慰。
  • type: eqref是一件好事。这意味着它使用PRIMARY KEY索引或UNIQUE KEY索引来准确查找一行。请注意列rows: 1。因此,至少对于前一个连接中的每一行,它只进行一次索引查找。

  • 您应该按顺序在calenderattendee上为列(CalenderId,StaffId)创建索引(@ spencer7593在我撰写帖子时发布了此建议)。

  • 在此查询中使用LEFT [OUTER] JOIN,可以防止MySQL优化表连接的顺序。由于您的查询提取h.name,我推断您确实只想要日历事件有与会者的结果,并且与会者具有相应的用户记录。您没有使用INNER JOIN
  • 是没有意义的

这里是使用新索引的EXPLAIN,并且连接更改为INNER JOIN(尽管我的行数没有意义,因为我没有创建测试数据):

+----+-------------+-------+------------+--------+--------------------------------+----------------------+---------+----------------+------+----------+-----------------------+
| id | select_type | table | partitions | type   | possible_keys                  | key                  | key_len | ref            | rows | filtered | Extra                 |
+----+-------------+-------+------------+--------+--------------------------------+----------------------+---------+----------------+------+----------+-----------------------+
|  1 | SIMPLE      | a     | NULL       | index  | PRIMARY,ID_UNIQUE,idxHospital  | ID_UNIQUE            | 4       | NULL           |    1 |   100.00 | Using index           |
|  1 | SIMPLE      | e     | NULL       | ref    | idxCalStaffID,CalenderID       | CalenderID           | 4       | test.a.ID      |    1 |   100.00 | Using index           |
|  1 | SIMPLE      | f     | NULL       | eq_ref | PRIMARY,ID_UNIQUE              | PRIMARY              | 4       | test.e.StaffID |    1 |   100.00 | NULL                  |
|  1 | SIMPLE      | h     | NULL       | eq_ref | index_users_on_email,idx_email | index_users_on_email | 767     | func           |    1 |   100.00 | Using index condition |
+----+-------------+-------+------------+--------+--------------------------------+----------------------+---------+----------------+------+----------+-----------------------+

calenderattendee表的type: index已更改为type: ref,这意味着针对非唯一索引进行索引查找。关于Using join buffer的说明已经消失。

那应该会更好。

  

索引的排序如何影响获取时间..?

想一下电话簿,先按姓氏排序,然后按名字排序。这有助于您快速查找姓氏。但它并没有帮助你以名字查找人。

索引中列的位置很重要!

您可能会喜欢我的演示如何设计索引,真的

答案 1 :(得分:1)

问:我是否缺少任何索引,因此我的查询速度很慢?

A:是的。缺少calendarattendee的合适索引。

我们可能希望calenderattendee上的索引为calendarid作为前导列,例如:

 ... ON calenderattendee (calendaid, staffid)

答案 2 :(得分:1)

这似乎是内部联接可能比左联接更好的选择。

SELECT a.ID, h.name
    FROM `stjude`.`calender` a 
    INNER JOIN calenderattendee e on a.ID = e.calenderID
    INNER JOIN callplanstaff f on e.StaffID = f.ID
    INNER JOIN users h on f.Staffname = h.email

然后让我们进入索引。日历表有

PRIMARY KEY (`ID`),
UNIQUE KEY `ID_UNIQUE` (`ID`),

第二个,ID_UNIQUE是多余的。主键是唯一索引。索引太多会降低插入/更新/删除操作的速度。

然后users表有

UNIQUE KEY `index_users_on_email` (`email`),
UNIQUE KEY `index_users_on_name` (`name`),
KEY `idx_email` (`email`) USING BTREE KEY_BLOCK_SIZE=100

这里的idx_email列是多余的。除此之外,通过调整索引没有太多事情要做。您的解释显示正在每个表和表上使用索引。

  

为什么MYSQL在callplanstaff表和users表上没有使用索引?

你的解释表明确实如此。它在这些表上使用主键和index_users_on_email索引。

  

另外,在我的情况下,我应该使用多索引而不是多列   索引?

根据经验,mysql每个表只使用一个索引。因此,多列索引是一种方法,而不是具有多个索引。

  

是否有我失踪的索引,所以我的查询很慢?

正如我在评论中提到的那样,你正在获取(并且可能显示)13,000条记录。这可能是你的瓶颈所在。