优化无法解释的MySQL查询速度

时间:2012-02-09 21:11:50

标签: mysql query-optimization subquery aggregate-functions

我在一个愚蠢的查询中失去了头发。首先,我会解释它的目标是什么。我每小时都会获取一组值并存储在数据库中。这些值可以随时间增加或保持相等。此查询最近60天提取最新值(我有双胞胎查询提取最新值的数周和月,它们是相似的)。该查询是不言自明的:

SELECT l.value AS value
FROM atable AS l
WHERE l.time = (
                  SELECT MAX(m.time)
                  FROM atable AS m
                  WHERE DATE(l.time) = DATE(m.time) 
                  LIMIT 1
               )
ORDER BY l.time DESC 
LIMIT 60

看起来并不特别。但它非常慢(> 30秒),考虑到time是一个索引,表包含的行少于5000行。而且我确定问题在于子查询。

noob错误在哪里?


更新1 :如果我使用MAX()避免SELECT m.time ... ORDER BY m.time DESC,则情况相同。

更新2 :多次调用DATE()函数似乎不是问题。我试图创建一个计算字段day DATEUPDATE atable SET day = DATE(time)的运行时间不到2秒。带有l.day = m.day(无函数!)的修改后的查询在与之前完全相同的时间内运行。


6 个答案:

答案 0 :(得分:2)

我看到的主要问题是使用DATE()子句中表达式左侧的WHERE。在DATE()表达式的两侧使用函数WHERE显式阻止MySQL在日期字段上使用索引。相反,它必须扫描所有行以在每一行上应用该函数。

而不是:

WHERE DATE(l.time) = DATE(m.time) 

尝试这样的事情:

WHERE l.time BETWEEN
  DATE_SUB(m.date, INTERVAL TIME_TO_SEC(m.date) SECOND)
  AND DATE_ADD(DATE_SUB(m.date, INTERVAL TIME_TO_SEC(m.date) SECOND), INTERVAL 86399 SECOND)

也许你知道一种更好的方法可以将m.date转换为2012-02-09 00:00:002012-02-09 23:59:59之类的范围,而不是上面的例子,但想法是你要保留左侧的表达式作为原始列名,在这种情况下为l.time,并在右侧以两个常量(或两个可转换为常量的表达式)的形式给出一个范围。

修改

我正在使用您预先计算的day字段:

SELECT *
FROM atable a
WHERE a.time IN
(SELECT MAX(time)
FROM atable
GROUP BY day
ORDER BY day DESC
LIMIT 60)

至少在这里,内部查询只运行一次,然后使用IN cluase完成二进制搜索。你仍然在扫描表格,但只有一次,内部查询只运行一次的优势可能会产生很大的影响。

如果您知道自己每天都有值,则可以通过添加WHERE子句,将其限制为过去60个日历日,并丢失LIMIT 60来改进内部查询。确保将daytime编入索引。

答案 1 :(得分:1)

不使用MAX(m.time)而是在子选择

中执行以下操作
SELECT m.time
FROM table AS m
WHERE DATE(l.time) = DATE(m.time)
ORDER BY m.time DESC
LIMIT 1

这可能有助于加快查询速度,因为它为查询解析器提供了另一种选择

然而另一件我注意到的是你正在使用DATE(l.time)和DATE(m.time),如果你的索引没有在DATE(m.time)创建,那么你将不会使用索引和因此可能会导致缓慢。

答案 2 :(得分:1)

根据反馈答案,如果条目是按日期/时间顺序添加的,与自动增量ID直接相关,谁关心TIME ...获取auto-inc号码进行精确,非模糊的连接

select
      A1.AutoID,
      A1.time,
      A1.Value
   from
      ( select date( A2.time ) as SingleDate,
               max( A2.AutoID ) as MaxAutoID
           from aTable A2
           where date( A2.Time ) >= date( date_sub( now(), interval 60 day ))
           group by date( A2.time ) ) into MaxPerDate
      JOIN aTable A1
         on MaxPerDate.MaxAutoID = A1.AutoID
   order by
      A1.AutoID DESC

答案 3 :(得分:0)

您可以使用“explain”语句让mysql告诉您它正在做什么。

EXPLAIN SELECT  l.value AS value
        FROM    table AS l
        WHERE   l.time = (
                   SELECT MAX(m.time)
                   FROM   table AS m
                   WHERE  DATE(l.time) = DATE(m.time) LIMIT 1
                )
        ORDER BY l.time DESC LIMIT 60

至少应该让你深入了解哪里可以进一步了解。

答案 4 :(得分:0)

如果您有time的索引,我建议您使用TOP 1代替MAX,如下所示:

SELECT  l.value AS value
FROM    table AS l
WHERE   l.time = (
               SELECT TOP 1 m.time
               FROM   table AS m
               ORDER BY m.time DESC LIMIT 1
             )
ORDER BY l.time DESC LIMIT 60

答案 5 :(得分:0)

您的外部查询使用的是没有索引的文件集。 尝试更改为InnoDB引擎,看看它是否能改进。

快速测试:

mysql> show create table atable\G
*************************** 1. row ***************************
       Table: atable
Create Table: CREATE TABLE `atable` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `t` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `t` (`t`)
) ENGINE=InnoDB AUTO_INCREMENT=51 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

mysql> explain SELECT id FROM atable AS l WHERE l.t = (                   SELECT MAX(m.t)                   FROM atable AS m                   WHERE DATE(l.t) = DATE(m.t)                    LIMIT 1                ) ORDER BY l.t DESC  LIMIT 50;
+----+--------------------+-------+-------+---------------+------+---------+------+------+--------------------------+
| id | select_type        | table | type  | possible_keys | key  | key_len | ref  | rows | Extra                    |
+----+--------------------+-------+-------+---------------+------+---------+------+------+--------------------------+
|  1 | PRIMARY            | l     | index | NULL          | t    | 4       | NULL |   50 | Using where; Using index |
|  2 | DEPENDENT SUBQUERY | m     | index | NULL          | t    | 4       | NULL |   50 | Using where; Using index |
+----+--------------------+-------+-------+---------------+------+---------+------+------+--------------------------+
2 rows in set (0.00 sec)

After changing to MyISAM:

mysql> explain SELECT id FROM atable AS l WHERE l.t = (                   SELECT MAX(m.t)                   FROM atable AS m                   WHERE DATE(l.t) = DATE(m.t)                    LIMIT 1                ) ORDER BY l.t DESC  LIMIT 50;
+----+--------------------+-------+-------+---------------+------+---------+------+------+-----------------------------+
| id | select_type        | table | type  | possible_keys | key  | key_len | ref  | rows | Extra                       |
+----+--------------------+-------+-------+---------------+------+---------+------+------+-----------------------------+
|  1 | PRIMARY            | l     | ALL   | NULL          | NULL | NULL    | NULL |   50 | Using where; Using filesort |
|  2 | DEPENDENT SUBQUERY | m     | index | NULL          | t    | 4       | NULL |   50 | Using where; Using index    |
+----+--------------------+-------+-------+---------------+------+---------+------+------+-----------------------------+
2 rows in set (0.00 sec)