MySQL查询卡在“发送数据”上,似乎从未完成

时间:2018-07-14 11:05:10

标签: mysql query-performance

我有一个查询,它遍历book_records表中的每个小时,并获取已拿起的书数(值= 1)与可用的书数(值= 0):

SELECT sr.time AS h,
       COUNT(CASE WHEN sr.value = 1 THEN 1 END) AS taken,
       COUNT(CASE WHEN sr.value = 0 THEN 1 END) AS free,
       sr.libID AS libID
FROM `book_records` AS sr
WHERE sr.time BETWEEN '2017-01-01 00:00:00' AND '2017-01-01 23:00:00' 
AND MINUTE(sr.time) = 0
AND libID = 0 
GROUP BY h, libID
ORDER BY h, libID

这里要看的主要内容是

WHERE sr.time BETWEEN '2017-01-01 00:00:00' AND '2017-01-01 23:00:00' 

这大约需要0.03s来查询。

如果我将其更改为此(5天):

WHERE sr.time BETWEEN '2017-01-01 00:00:00' AND '2017-01-05 23:00:00' 

大约需要0.15秒。

10天:

WHERE sr.time BETWEEN '2017-01-01 00:00:00' AND '2017-01-10 23:00:00'

大约需要0.28s

但在15天后:

WHERE sr.time BETWEEN '2017-01-01 00:00:00' AND '2017-01-15 23:00:00'

在撰写本文时,查询仍在进行中。 SHOW FULL PROCESSLIST告诉我,查询的状态随着时间的增加而被设置为“睡眠”。

那有什么用呢?我的MySQL 5.7配置中是否有可能导致此问题的原因?查询速度可以分别为1天,5天和10天,但是还可以再增加5天(15),查询是否进入睡眠状态?为什么?

编辑:显示全部过程列表输出:

| 2234 | phpmyadmin | localhost | NULL    | Sleep   |     4 |              | NULL
| 2235 | root       | localhost | library | Query   |     4 | Sending data | 
SELECT SQL_CALC_FOUND_ROWS sr.time AS h, 
COUNT(CASE WHEN sr.value = 1 THEN 1 END) AS taken, 
COUNT(CASE WHEN sr.value = 0 THEN 1 END) AS free, sr.libID AS libID FROM `book_records` AS sr
WHERE sr.time BETWEEN '2017-01-01 00:00:00' AND '2017-01-15 23:00:00' 
AND MINUTE(sr.time) = 0
AND libID = 0 
GROUP BY h, libID
ORDER BY h, libID LIMIT 1 |

1 个答案:

答案 0 :(得分:1)

让我们看看您的查询。

SELECT sr.time AS h, 
       COUNT(CASE WHEN sr.value = 1 THEN 1 END) AS taken, 
       COUNT(CASE WHEN sr.value = 0 THEN 1 END) AS free, 
       sr.libID AS libID
  FROM `book_records` AS sr
WHERE sr.time BETWEEN '2017-01-01 00:00:00' AND '2017-01-01 23:00:00' 
  AND MINUTE(sr.time) = 0
  AND libID = 0 
GROUP BY h, libID
ORDER BY h, libID

首先,您需要在book_records (libID, time)上使用复合索引来进行查询,因为sargable. Unsargable查询需要全表扫描,这很慢。

接下来,您的选择标准sr.time BETWEEN '2017-01-01 00:00:00' AND '2017-01-01 23:00:00'有点奇怪。获取2017年7月1日的所有记录。您需要使用这些条件

       sr.time >= '2017-01-01 00:00:00'
   AND sr.time <  '2017-01-02 00:00:00'

为什么?您希望所有记录的时间开始于7月1日午夜,直到但不包括7月2日午夜。

接下来,您将具有此选择标准。 MINUTE(sr.time) = 0。它从结果集中删除许多记录,并保留其他记录。例如,它删除时间为10:03:00的记录,并保留时间为10:00:59的记录。那可能不是您想要的。消除该标准;它使查询无法进行查询,并选择一组奇怪的行。

接下来,您似乎正在尝试按一天中的小时来汇总结果。为此,您需要GROUP BY HOUR(sr.time)。即使您在过滤器范围内包含多天,这也将为您提供一天中每小时的记录数。

第四,同样重要的是,您可以通过在三列(libID, time, value)而不是仅两列上创建索引来使查询更快。之所以称为covering index,是因为它包含了查询所需的所有行。可以完全从索引满足查询,因此MySQL不需要同时读取索引和表。使用

创建此索引
   CREATE INDEX book_records_id_time_val ON book_records (libID, time, value);

在此末尾,您的查询看起来像

SELECT HOUR(sr.time) AS h, 
       COUNT(CASE WHEN sr.value = 1 THEN 1 END) AS taken, 
       COUNT(CASE WHEN sr.value = 0 THEN 1 END) AS free, 
       sr.libID AS libID
  FROM `book_records` AS sr
 WHERE sr.time >= '2017-01-01 00:00:00'
   AND sr.time <  '2017-01-02 00:00:00'
   AND libID = 0 
 GROUP BY HOUR(sr.time), libID
 ORDER BY HOUR(sr.time), libID

这些更改应使您的查询更准确,并且很多更快。

通过查看正在运行的查询的方式,您必须从与运行查询的MySQL连接不同的MySQL连接(另一个客户端程序或另一个查询窗口)发出SHOW FULL PROCESSLIST。当连接显示sleep时,它什么都没做。