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

Ale*_*cob 11 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 |
+----+-------------+--------+-------+------------------------+---------------+---------+------+------+-------------+
Run Code Online (Sandbox Code Playgroud)

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 |
+----+-------------+--------+-------+------------------------+-----------+---------+------+-------+----------------------------------------------------+
Run Code Online (Sandbox Code Playgroud)

Gor*_*off 9

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

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

然后当你添加索引时creation_date,MySQL决定使用该索引代替order by.这需要读取索引中的每一行,然后获取相应的数据页以检查where条件并返回列(如果匹配).这种读取效率非常低,因为它不是"页面"顺序,而是由索引指定.随机读取可能效率很低.

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

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

您可以通过索引来使所有这些查询运行得更快orders(id_state, id_mp, creation_date).该where子句将使用前两列,order by将使用最后一列.


Li *_*ing 5

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

请参阅: MySQL ORDER BY / LIMIT 性能:后期行查找

所以,解决办法是:

(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
Run Code Online (Sandbox Code Playgroud)

在我的数据库中,

使用“LIMIT 21560, 20”选择所有列的旧 SQL 大约需要 4.484 秒。

新的 sql 仅需 0.063 秒。新的大约快 71 倍


Tam*_*ama 5

我在包含 250 万条记录的表上遇到了类似的问题。删除限制部分查询花费了几秒钟。由于限制部分,它永远卡住了。

我用子查询解决了。在你的情况下它将变成:

SELECT * 
FROM 
    (SELECT * 
     FROM orders 
     WHERE id_state = 2 
       AND id_mp IS NOT NULL 
     ORDER BY creation_date DESC) tmp 
LIMIT 200
Run Code Online (Sandbox Code Playgroud)

我注意到,当所选行数大于限制参数时,原始查询速度很快。当 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
Run Code Online (Sandbox Code Playgroud)