繁体   English   中英

为什么这个查询需要超过 5 秒才能运行?

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM