MySQL左外连接很慢

Posted

技术标签:

【中文标题】MySQL左外连接很慢【英文标题】:MySQL left outer join is slow 【发布时间】:2011-01-13 19:47:37 【问题描述】:

希望得到有关此查询的一些帮助,我已经研究了一段时间,但无法更快地得到它:

SELECT date, count(id) as 'visits' FROM dates 
LEFT OUTER JOIN visits 
ON (dates.date = DATE(visits.start) and account_id = 40 ) 
WHERE date >= '2010-12-13' AND date <= '2011-1-13' 
GROUP BY date ORDER BY date ASC

该查询大约需要 8 秒才能运行。我在 dates.date、visits.start、visits.account_id 和visits.start+visits.account_id 添加了索引,但无法让它运行得更快。

表结构(只显示访问表中的相关列):

create table visits (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `account_id` int(11) NOT NULL,
    `start` DATETIME NOT NULL,
    `end` DATETIME NULL,
    PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

CREATE TABLE `dates` (
  `date` date NOT NULL,
  PRIMARY KEY (`date`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

dates 表包含从 2010-1-1 到 2020-1-1 的所有日期(约 3k 行)。访问表包含从 2010-6-1 到昨天的大约 400k 行。我正在使用日期表,因此在没有访问的日子里,加入将返回 0 次访问。

我想参考的结果:

+------------+--------+
| date       | visits |
+------------+--------+
| 2010-12-13 |    301 |
| 2010-12-14 |    356 |
| 2010-12-15 |    423 |
| 2010-12-16 |    332 |
| 2010-12-17 |    346 |
| 2010-12-18 |    226 |
| 2010-12-19 |    213 |
| 2010-12-20 |    311 |
| 2010-12-21 |    273 |
| 2010-12-22 |    286 |
| 2010-12-23 |    241 |
| 2010-12-24 |    149 |
| 2010-12-25 |    102 |
| 2010-12-26 |    174 |
| 2010-12-27 |    258 |
| 2010-12-28 |    348 |
| 2010-12-29 |    392 |
| 2010-12-30 |    395 |
| 2010-12-31 |    278 |
| 2011-01-01 |    241 |
| 2011-01-02 |    295 |
| 2011-01-03 |    369 |
| 2011-01-04 |    438 |
| 2011-01-05 |    393 |
| 2011-01-06 |    368 |
| 2011-01-07 |    435 |
| 2011-01-08 |    313 |
| 2011-01-09 |    250 |
| 2011-01-10 |    345 |
| 2011-01-11 |    387 |
| 2011-01-12 |      0 |
| 2011-01-13 |      0 |
+------------+--------+

提前感谢您的帮助!

【问题讨论】:

mysql手册中查找explainexplain extended 【参考方案1】:

你的问题在这里:

ON (dates.date = DATE(visits.start) and account_id = 40 ) 

由于您在visits.start 上使用DATE 函数,MySQL 无法使用索引进行连接。

可能最好的解决方案是将start_dateend_date 列添加到dates 表并索引这些列。因此,对于日期为 2011-01-01 的行,开始日期为 2011-01-01 00:00:00,结束日期为 2011-01-01 23:59:59。

然后你可以像这样直接加入日期表:

SELECT date, count(id) as 'visits' FROM dates 
LEFT OUTER JOIN visits 
ON (visits.start BETWEEN dates.start_date AND dates.end_date and account_id = 40 ) 
WHERE date >= '2010-12-13' AND date <= '2011-1-13' 
GROUP BY date ORDER BY date ASC

另一种选择是将日期和时间部分分别存储在访问表中,并仅使用日期部分进行连接。

【讨论】:

谢谢,成功了。我在访问表中添加了一个start_date 列并在其上添加了一个索引。低至 300 毫秒!【参考方案2】:

我认为这主要是因为 DATE() 函数很慢。您可以将日期列添加到存储整个日期的访问,并编写触发器以在插入访问或其日期时间时自动更新它。这将允许 MySQL 更好地利用连接中使用的索引。

【讨论】:

【参考方案3】:

这样的事情怎么样:对 select from eumiro 的结果进行外部联接?

SELECT date, v.visits as 'visits' FROM dates 
LEFT OUTER JOIN (SELECT DATE(start) as dt, count(id) as 'visits'
FROM visits 
WHERE account_id = 40
AND date BETWEEN '2010-12-13' AND '2011-01-13' 
GROUP BY DATE(start)
ORDER BY 1)
v
ON (dates.date = v.dt ) 
WHERE date >= '2010-12-13' AND date <= '2011-1-13' 

编辑:编辑的 SQL 编辑:另一个选项 - 内联选择,类似这样:

SELECT date, (select count(*)  as 'visits' 
FROM  from visits 
where date = DATE(visits.start) and account_id = 40 ) 
) from dates
WHERE date >= '2010-12-13' AND date <= '2011-1-13' 
ORDER BY date ASC

【讨论】:

以上是关于MySQL左外连接很慢的主要内容,如果未能解决你的问题,请参考以下文章

对两个 MySQL 查询执行左外连接?

mysql左外连接

使用子查询改进 MySql 查询左外连接

MySQL左外连接与where子句 - 返回不匹配的行

真正的左外连接

内连接与左外链接的区别