在我的查询中使用LIMIT时为什么MySQL会变慢?

时间:2013-07-19 13:53:19

标签: mysql sql performance

我正在试图找出为什么我的查询之一很慢以及如何解决它但我对我的结果感到有些困惑。

我有一个orders表,大约有80列和775179行,我正在执行以下请求:

SELECT * FROM orders WHERE id_state = 2 AND id_mp IS NOT NULL ORDER BY creation_date DESC LIMIT 200

在4.5s中返回38行

删除ORDER BY时我得到了很好的改进:

SELECT * FROM orders WHERE id_state = 2 AND id_mp IS NOT NULL LIMIT 200

0.30秒内的38行

但是在不触及LIMIT的情况下移除ORDER BY时,我的效果会更好:

SELECT * FROM orders WHERE id_state = 2 AND id_mp IS NOT NULL ORDER BY creation_date DESC

0.10s(?? ??)中的38行

为什么我的LIMIT如此饥饿?

进一步

我在发送答案之前尝试了一些事情,并在注意到我在creation_date上有一个索引(这是一个datetime)后我删除了它,第一个查询现在运行在0.10秒。那是为什么?

修改

很好的猜测,我在其他列的部分有索引。

mysql> explain SELECT * FROM orders WHERE id_state = 2 AND id_mp IS NOT NULL ORDER BY creation_date DESC LIMIT 200;
+----+-------------+--------+-------+------------------------+---------------+---------+------+------+-------------+
| id | select_type | table  | type  | possible_keys          | key        | key_len | ref  | rows | Extra       |
+----+-------------+--------+-------+------------------------+---------------+---------+------+------+-------------+
|  1 | SIMPLE      | orders | index | id_state_idx,id_mp_idx | creation_date | 5       | NULL | 1719 | Using where |
+----+-------------+--------+-------+------------------------+---------------+---------+------+------+-------------+

1行(0.00秒)

mysql> explain SELECT * FROM orders WHERE id_state = 2 AND id_mp IS NOT NULL ORDER BY creation_date DESC;
+----+-------------+--------+-------+------------------------+-----------+---------+------+-------+----------------------------------------------------+
| id | select_type | table  | type  | possible_keys          | key       | key_len | ref  | rows  | Extra                                              |
+----+-------------+--------+-------+------------------------+-----------+---------+------+-------+----------------------------------------------------+
|  1 | SIMPLE      | orders | range | id_state_idx,id_mp_idx | id_mp_idx | 3       | NULL | 87502 | Using index condition; Using where; Using filesort |
+----+-------------+--------+-------+------------------------+-----------+---------+------+-------+----------------------------------------------------+

4 个答案:

答案 0 :(得分:6)

索引不一定能提高性能。为了更好地了解正在发生的事情,如果您为不同的查询添加explain,将会有所帮助。

我最好的猜测是,您可以使用id_stateid_state, id_mp中的索引来满足where子句。如果是这样,没有order by的第一个查询将使用此索引。它应该很快。即使没有索引,也需要对orders表中的页面进行顺序扫描,这仍然可以非常快。

然后,当您在creation_date上添加索引时,MySQL决定将该索引用于order by。这需要读取索引中的每一行,然后获取相应的数据页以检查where条件并返回列(如果匹配)。这种读数非常低效,因为它不在" page"订单,而不是索引指定。随机读取可能效率很低。

更糟糕的是,即使您有limit,仍然需要阅读整个表,因为需要整个结果集。虽然您已经在38条记录上保存了一个排序,但是您创建了一个非常低效的查询。

顺便说一句,如果orders表不适合可用内存,这种情况会变得更糟。然后你有一个名为" thrashing"的条件,其中每个新记录往往会产生一个新的I / O读取。因此,如果一个页面上有100条记录,则该页面可能必须被读取100次。

通过在orders(id_state, id_mp, creation_date)上建立索引,您可以更快地运行所有这些查询。 where子句将使用前两列,order by将使用最后一列。

答案 1 :(得分:0)

我的项目中发生了同样的问题, 我做了一些测试,发现由于行查找,LIMIT很慢

请参阅: MySQL ORDER BY / LIMIT performance: late row lookups

所以,解决方案是:

(A)使用LIMIT时,不要选择所有列,只选择PK列

(B)选择所需的所有列,然后使用(A)

的结果集连接

SQL应该喜欢:

SELECT
    *
FROM
    orders O1   <=== this is what you want
JOIN
    (
        SELECT
            ID                         <== fetch the PK column only, this should be fast
        FROM
            orders
        WHERE
            [your query condition]     <== filter record by condition
        ORDER BY
            [your order by condition]  <== control the record order
        LIMIT 2000, 50                 <== filter record by paging condition
    ) as O2
ON
    O1.ID = O2.ID
ORDER BY
    [your order by condition]          <== control the record order

在我的数据库中,

使用“LIMIT 21560,20”选择所有列的旧SQL,成本约为4.484s。

新的sql仅花费0.063秒。新的

快了71倍

答案 2 :(得分:0)

我在250万条记录的表上遇到了类似的问题。移除限制部分,耗时数秒钟。有了极限部分,它就永远卡住了。

我用子查询解决了。在您的情况下,它将变为:

select * from (SELECT * FROM orders WHERE id_state = 2 AND id_mp IS NOT NULL ORDER BY creation_date DESC) tmp LIMIT 200

予指出的是,原始查询是快时选择的行数比限制参数越大。当limit参数无用时,查询变得非常缓慢。


另一个解决方案是尝试强制索引。您可以尝试使用

SELECT * FROM orders force index (id_mp_idx) WHERE id_state = 2 AND id_mp IS NOT NULL ORDER BY creation_date DESC LIMIT 200

答案 3 :(得分:0)

问题是 mysql 被迫动态排序数据。我对深度偏移的查询如下:

ORDER BY somecol LIMIT 99990, 10

花了 2.5 秒。

我通过创建一个新表来修复它,该表已按列 somecol 预排序数据并且仅包含 id,并且深度偏移(无需使用 ORDER BY)需要 0.09 秒。

0.1s 仍然不够快。 0.01s 会更好。

我最终会创建一个表,将页码保存为特殊索引列,因此我将查询 where page = Z,而不是限制 x,y。

我刚试过,速度快到 0.0013。唯一的问题是,偏移量是基于静态数字的(例如,按 10 个项目在页面中预先排序......不过问题不大......你仍然可以获取任何页面的任何数据。)