[英]Why does my query take over 30 mins on MySQL 5.7 but a couple seconds on MySQL 8?
[英]Why does this query take over 5 seconds to run?
我有一个 MySQL 表,里面有大约 2m 行。 我正在尝试运行以下查询,每次需要超过 5 秒才能获得结果。 我在created_at
列上有一个索引。 下面是解释EXPLAIN
。
这是预期的吗?
提前致谢。
SELECT
DATE(created_at) AS grouped_date,
HOUR(created_at) AS grouped_hour,
count(*) AS requests
FROM
`advert_requests`
WHERE
DATE(created_at) BETWEEN '2022-09-09' AND '2022-09-12'
GROUP BY
grouped_date,
grouped_hour
EXPLAIN 显示type: index
,它是一个索引扫描。 也就是说,它正在使用索引,但它遍历索引中的每个条目,就像表扫描对表中的行所做的那样。 这由rows: 2861816
,它告诉您优化器对它将检查的索引条目数量的估计(这是一个粗略的数字)。 这比只检查与条件匹配的行要昂贵得多,这是我们从索引中寻找的好处。
那么这是为什么呢?
当您在搜索中的索引列上使用任何 function 时,如下所示:
WHERE
DATE(created_at) BETWEEN '2022-09-09' AND '2022-09-12'
它破坏了索引减少检查行数的好处。
MySQL 的优化器对函数的结果没有任何智能,因此它无法推断返回值的顺序将与索引的顺序相同。 因此它不能使用索引排序的事实来缩小搜索范围。 您和我都知道DATE(created_at)
与created_at
的顺序相同是很自然的,但查询优化器不知道这一点。 还有像MONTH(created_at)
这样的其他函数,其结果肯定不是按排序顺序排列的,并且 MySQL 的优化器不会尝试知道哪个函数的结果是可靠排序的。
要修复您的查询,您可以尝试以下两种方法之一:
使用表达式索引。 这是 MySQL 8.0 中的新功能:
ALTER TABLE `advert_requests` ADD INDEX ((DATE(created_at)))
注意多余的一对括号。 这些是定义表达式索引时所必需的。 索引条目是 function 或表达式的结果,而不是列的原始值。
如果您在查询中使用相同的表达式,优化器会识别并使用索引。
mysql> explain SELECT DATE(created_at) AS grouped_date, HOUR(created_at) AS grouped_hour, count(*) AS requests FROM `advert_requests` WHERE DATE(created_at) BETWEEN '2022-09-09' AND '2022-09-12' GROUP BY grouped_date, grouped_hour\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: advert_requests
partitions: NULL
type: range <-- much better than 'index'
possible_keys: functional_index
key: functional_index
key_len: 4
ref: NULL
rows: 1
filtered: 100.00
Extra: Using where; Using temporary
如果使用MySQL 5.7,则不能直接使用表达式索引,但可以使用虚拟列,并在虚拟列上定义索引:
ALTER TABLE advert_requests
ADD COLUMN created_at_date DATE AS (DATE(created_at)),
ADD INDEX (created_at_date);
优化器识别表达式的技巧仍然有效。
如果您使用早于 5.7 的 MySQL 版本,则无论如何都应该升级。 MySQL 5.6 和更早的版本到现在已经过了生命周期,它们存在安全风险。
您可以做的第二件事是重构您的查询,使created_at
列不在 function 内。
WHERE
created_at >= '2022-09-09' AND created_at < '2022-09-13'
将日期时间与日期值进行比较时,日期值隐含在 00:00:00.000 时间。 要包括直到 2022-09-12 23:59:59.999 的每一秒,使用< '2022-09-13'
会更简单。
对此的解释表明它使用created_at
上的现有索引。
mysql> explain SELECT DATE(created_at) AS grouped_date, HOUR(created_at) AS grouped_hour, count(*) AS requests FROM `advert_requests` WHERE created_at >= '2022-09-09' AND created_at < '2022-09-13' GROUP BY grouped_date, grouped_hour\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: advert_requests
partitions: NULL
type: range <-- not 'index'
possible_keys: created_at
key: created_at
key_len: 6
ref: NULL
rows: 1
filtered: 100.00
Extra: Using index condition; Using temporary
此解决方案适用于旧版本的 MySQL 以及 5.7 和 8.0。
如果我正确理解了EXPLAIN
,它就可以使用索引来实现WHERE
过滤。 但这将返回 280 万行,然后必须按日期和小时分组,这是一个缓慢的过程。
您可以通过为日期和小时创建虚拟列来改进它,并为它们建立索引。
ALTER TABLE advert_requests
ADD COLUMN created_date AS DATE(created_at), ADD column created_hour AS HOUR(created_at), ADD INDEX (created_date, created_hour);
使用explain analysis
,检查是否是Index range scan
。 如果不点击此链接:https://dev.mysql.com/doc/refman/8.0/en/range-optimization.html选定的日期范围。据我所知,在这种情况下优化并非易事)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.