繁体   English   中英

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

[英]Optimizing unexplainably slow MySQL query

我在一个愚蠢的问题上失去了头发。 首先,我会解释它的目标是什么。 我每小时都会获取一组值并存储在数据库中。 这些值可以随时间增加或保持相等。 该查询提取最新价值日益为最新的超过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 :如果我使用SELECT m.time ... ORDER BY m.time DESC避免使用MAX()则情况相同。

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


我看到的主要问题是在WHERE子句中的表达式左侧使用DATE() WHERE表达式的两侧使用函数DATE()显式阻止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编入索引。

而不是使用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)创建,那么你将不会使用索引,因此可能导致缓慢。

根据反馈答案,如果条目按日期/时间顺序添加,直接与自动增量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

您可以使用“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

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

如果您有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

您的外部查询使用没有索引的filesort。 尝试更改为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)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM