MySQL 查询卡在 'Sending Data',似乎从未完成
MySQL Query stuck on 'Sending Data', doesn't seem to ever finish
我有一个查询,查询我 book_records
table 的每个小时,并获取已使用的书籍数量(值 = 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.03 秒。
如果我改成这样(5天):
WHERE sr.time BETWEEN '2017-01-01 00:00:00' AND '2017-01-05 23:00:00'
大约需要0.15s。
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
告诉我查询的状态设置为 'Sleep' 随着时间的推移。
那么是什么原因呢?在我的 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 |
让我们看看您的查询。
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 查询需要完全 table 扫描,这很慢。
其次,您的选择标准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 不需要同时读取索引和 table。使用
创建此索引
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 连接(另一个客户端程序或另一个查询 window)发出 SHOW FULL PROCESSLIST
运行 查询。当连接显示 sleep
它没有做任何事情。
我有一个查询,查询我 book_records
table 的每个小时,并获取已使用的书籍数量(值 = 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.03 秒。
如果我改成这样(5天):
WHERE sr.time BETWEEN '2017-01-01 00:00:00' AND '2017-01-05 23:00:00'
大约需要0.15s。
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
告诉我查询的状态设置为 'Sleep' 随着时间的推移。
那么是什么原因呢?在我的 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 |
让我们看看您的查询。
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 查询需要完全 table 扫描,这很慢。
其次,您的选择标准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 不需要同时读取索引和 table。使用
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 连接(另一个客户端程序或另一个查询 window)发出 SHOW FULL PROCESSLIST
运行 查询。当连接显示 sleep
它没有做任何事情。