即使使用索引,mysql内部连接也比2个独立查询慢50-500倍

时间:2019-02-09 01:02:00

标签: mysql inner-join

我从内部联接中获得了非常出乎意料的糟糕性能,我不知所措,因为所有必要的索引似乎都就位,并且如果将其拆分为2个查询,它的运行速度至少快50倍。但是对我而言,关系数据库应该擅长的正是这种确切的查询类型。 MySQL 5.6是否重要。帐户表有约4K条记录,事件表有约500M条记录。

CREATE TABLE `tbl_account` (
  `accountID` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `accountKey` varchar(767) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL,
  ...,
  PRIMARY KEY (`accountID`),
  KEY `index_accountKey` (`accountKey`),
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE `tbl_event` (
  `eventID` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `fkAccount` bigint(20) unsigned DEFAULT NULL,
  `creationDate` datetime NOT NULL,
  ...,
  PRIMARY KEY (`eventID`),
  KEY `index_fkAccount` (`fkAccount`),
  KEY `index_creationDate` (`creationDate`),
  KEY `index_fkAccount_creationDate` (`fkAccount`, `creationDate`),
  CONSTRAINT `event_fkAccount` FOREIGN KEY (`fkAccount`) REFERENCES `tbl_account` (`accountID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

当我运行以下查询时,它至少需要5.5秒,有时需要近一分钟。

SELECT e.* FROM tbl_event e INNER JOIN tbl_account a ON e.fkAccount = a.accountID
WHERE a.accountKey = 'abcdefghij'
  AND e.creationDate >= '2019-02-01 00:00:00'
ORDER BY e.creationDate DESC
LIMIT 500;

如果我首先独立地查找accountID,则它始终只对第一个查询花费0.03s,对第二个查询花费0.1s(当我告诉它使用复合索引时,mysql有时只想使用index_creationDate独自)。

SELECT a.accountID FROM tbl_account a WHERE a.accountKey = 'abcdefghij';
/*returns accountID = 123*/

SELECT e.* FROM tbl_event e USE INDEX (index_fkAccount_creationDate)
WHERE e.fkAccount IN (123)
  AND e.creationDate >= '2019-02-01 00:00:00'
ORDER BY e.creationDate DESC
LIMIT 500;

一些注意事项:

  • 这不是因为缓存。我以不同的顺序运行它,使用了不同的日期和帐户过滤器,当我知道它没有被缓存时,结果是一致的。
  • 如果有关系,accountKey的accountID通常为1-1,但不完全相同,有些accountKeys具有多个accountID值。这就是为什么最后一个where子句使用IN的原因。
  • 我知道tbl_account中的index_accountKey不需要是varchar字段的全长即可保存索引大小,但这不是此问题的问题。
  • 在tbl_event中,由于复合索引作为第一个字段存在,因此index_fkAccount是多余的,但是它目前仍在我的真实表中,因此我也将其放在这里。

使用联接从选择中获得EXPLAIN的输出:

table       | type | key                          | ref                   |rows| Extra
tbl_account | ref  | index_accountKey             | const                 |001 | Using where; Using temporary; Using filesort
tbl_event   | ref  | index_fkAccount_creationDate | tbl_account.accountID |116 | Using index condition

为什么EXPLAIN说它将是“在哪里使用;使用临时文件;使用文件排序”来从tbl_account返回1行?当我自己查找tbl_account行时,它只是“在哪里使用”。我没有从tbl_account返回任何行作为订单的一部分,那么它试图排序的原因是什么?它不应该只是在tbl_account中查找accountID,然后将其插入tbl_event并在其中使用索引吗?

我尝试将过滤器放入ON子句。我试过使用子查询,但没有任何改善。这是MySQL的最差的优化,是否需要拆分查询,或者我可以做些什么来使其在联接中快速运行?

1 个答案:

答案 0 :(得分:0)

我很确定您的查询是

SELECT e.* 
FROM tbl_event e 
INNER JOIN tbl_account a ON e.fkAccount = a.accountID
WHERE a.accountKey = 'abcdefghij'
AND e.creationDate >= '2019-02-01 00:00:00'
ORDER BY e.creationDate DESC
LIMIT 500;

必须对tbl_event表进行表扫描(由creation_date限定),然后对于每一行,找到匹配的tbl_account行,并将给定值与tbl_account.accountKey值进行比较。

最好按如下所示逆转查询中表的顺序:

SELECT e.* 
FROM tbl_account a 
INNER JOIN tbl_event e ON e.fkAccount =a.accountID
WHERE a.accountKey='k3'
ORDER BY e.creationDate DESC
LIMIT 500;

这应该首先选择与给定密钥匹配的tbl_account行,然后找到与给定密钥对应的tbl_event行,它们是在给定时间戳记或之后创建的。

这是我测试的sqlfiddle link