使用LIMIT和Order BY时为什么MySQL查询会变慢?

时间:2016-07-20 06:22:52

标签: mysql database-performance

我有一个问题:

    SELECT abt.duration, a.id, a.a_id, a.a_tag
    FROM active_begin_times AS abt INNER JOIN sources AS a ON a.id = abt.a_source 
    AND a.u IN (29, 28, 27, 26, 25, 24) 
    WHERE (abt.duration > 86400000) 
    and (abt.begin_timestamp<=1465185600000 and 1465617600000<=abt.end_timestamp
    OR 1465185600000<=abt.begin_timestamp and abt.begin_timestamp<=1465617600000 
    OR 1465185600000<=abt.end_timestamp and abt.end_timestamp<=1465617600000) 
    order by abt.begin_timestamp asc LIMIT 0, 10

该数据库有大约500万条目。此查询大约需要30秒才能运行。但如果我按照我的订单更改为持续时间,则会在几秒内计算,如下面的查询:

SELECT abt.duration, a.id, a.a_id, a.a_tag
FROM active_begin_times AS abt INNER JOIN sources AS a ON a.id = abt.a_source 
AND a.u IN (29, 28, 27, 26, 25, 24) 
WHERE (abt.duration > 86400000) 
    and (abt.begin_timestamp<=1465185600000 and 1465617600000<=abt.end_timestamp
    OR 1465185600000<=abt.begin_timestamp and abt.begin_timestamp<=1465617600000 
    OR 1465185600000<=abt.end_timestamp and abt.end_timestamp<=1465617600000) 
    order by abt.duration asc LIMIT 0, 10

我在持续时间 begin_timestamp 上都有索引。当我将订单改为abt.id时,它又花了很多时间来计算,也有索引。

另外,我注意到的是这个特定的条件集,查询返回2行。但是如果我改变变量并且我得到这个查询返回20到30个奇数行,那么计算是瞬时的。

有人可以解释一下,以上2个案例?我试着查看,但无法理解行为或对解释不满意。

第一个查询的EXPLAIN返回:

| id | select_type | table | type   | possible_keys                                            | key             | key_len | ref                   | rows | Extra       |
|----|-------------|-------|--------|----------------------------------------------------------|-----------------|---------|-----------------------|------|-------------|
| 1  | SIMPLE      | abet  | index  | FK2681F9347A6A34B,begin_timestamp,end_timestamp,duration | begin_timestamp | 9       | \N                    | 6094 | Using where |
| 1  | SIMPLE      | a     | eq_ref | PRIMARY,FK722DBCA9F603AF                                 | PRIMARY         | 4       | db.abt.alarm_source   | 1    | Using where |

从我得到的第二个查询:

| id | select_type | table | type   | possible_keys                                            | key      | key_len | ref                   | rows | Extra                              |
|----|-------------|-------|--------|----------------------------------------------------------|----------|---------|-----------------------|------|------------------------------------|
| 1  | SIMPLE      | abet  | range  | FK2681F9347A6A34B,begin_timestamp,end_timestamp,duration | duration | 9       | \N                    | 8597 | Using index condition; Using where |
| 1  | SIMPLE      | a     | eq_ref | PRIMARY,FK722DBCA9F603AF                                 | PRIMARY  | 4       | db.abt.alarm_source   | 1    | Using where                        |

我可以看到的明显差异在Extras列中,在2个查询中Using index condition;,我不知道该怎么做。

SHOW CREATE TABLE的输出active_begin_time:

CREATE TABLE active_begin_times (
  id int(11) NOT NULL AUTO_INCREMENT,
  begin_timezone_offset int(11) DEFAULT NULL,
  begin_timezone_suffix varchar(100) DEFAULT NULL,
  begin_timestamp bigint(20) DEFAULT NULL,
  begin_timestamp_date varchar(100) DEFAULT NULL,
  duration bigint(20) DEFAULT NULL,
  end_timezone_offset int(11) DEFAULT NULL,
  end_timezone_suffix varchar(100) DEFAULT NULL,
  end_timestamp bigint(20) DEFAULT NULL,
  end_timestamp_date varchar(100) DEFAULT NULL,
  incomplete int(11) DEFAULT NULL,
  a_source int(11) DEFAULT NULL,
  PRIMARY KEY (id),
  KEY FK2681F9347A6A34B (alarm_source),
  KEY begin_timestamp (begin_timestamp),
  KEY end_timestamp (end_timestamp),
  KEY begin_timezone_offset (begin_timezone_offset),
  KEY end_timezone_offset (end_timezone_offset),
  KEY duration (duration),
  KEY begin_timestamp_date (begin_timestamp_date),
  KEY end_timestamp_date (end_timestamp_date)
) ENGINE=MyISAM AUTO_INCREMENT=6164640 DEFAULT CHARSET=latin1

3 个答案:

答案 0 :(得分:1)

对于第一个查询,索引用于按索引指定的顺序查找和读取整个表行,然后针对每一行测试整个where子句。所以它就像索引指定的顺序的全表扫描一样。

第二个更快,因为它使用索引来限制第一个地方(type: range)的行,并且它在存储引擎级别(extras: using index condition)执行此操作而不读取整个表要测试(abt.duration > 86400000)的行。对于那些与第一个条件匹配的行,将读取完整的表行以测试其余的where子句。

如果不比较解释结果,如果比较where子句的两个部分,很容易就可以使用第一部分(abt.duration > 86400000)的索引,但不能使用第二部分abt.begin_timestamp<=1465185600000,而不是第二部分1465185600000<=abt.begin_timestamp 1}}和end_timestamp甚至是begin_timestamp上的第三个条件而不是Average Duration (days) := CALCULATE ( AVERAGEX ( Cases, MAX ( DateTable[Month Ending] ) - Cases[dtopened] ), FILTER ( Cases, Cases[OpenClosedFlag] = "Open" ), FILTER ( Cases, Cases[dtopened] <= MAX ( DateTable[Month Ending] ) ) )

进一步阅读:Index Condition Pushdown Optimization

答案 1 :(得分:1)

WHERE    duration > 1234 AND ...
ORDER BY duration LIMIT 10

可以非常有效地使用INDEX(duration)

  1. 在索引中找到1234
  2. 抓住满足WHERE其余部分的下10行。然后停止。注意:如果还有其他过滤(AND ...),,那么即将推出10行,这很快。另一方面,如果10行排在表格的后面,那么这很慢。
  3. 您的其他情况更复杂,因为begin_timestamp不在简单的where子句中。相反,它必须:

    1. 查找与WHERE匹配的所有行(EXPLAIN估算数千)。
    2. 根据begin_timestamp(没有索引有用)
    3. 对它们进行排序
    4. 提供10行。
    5. 事实上,这个可能比上面的“慢”情况更快。它取决于数据值的分布以及优化器不够了解的其他内容。所以......有时候优化器会选择“错误”的方式来评估这种查询,并且最终会比应该的速度慢。

      关于“覆盖”索引的评论不正确,因为它不包含a_source

答案 2 :(得分:0)

因为你的表在duration上有一个索引(而不是另一个),所以它获得了前10行,并且完成了。事实上,它有一个covering索引,非常快。它是composite密钥的一部分,满足查询中的所有内容,没有跳转到数据页面。当然,有一个加入。涉及两张表。