我有一个MySQL表,其中有大约200万行.我正在try 运行下面的查询,每次都要花费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

enter image description here

推荐答案

解释显示type: index,这是一个索引扫描.也就是说,它使用了索引,但它迭代了索引中的every个条目,就像表扫描对表中的行所做的那样.rows: 2861816支持这一点,它告诉您优化器对它将判断的索引项数量的估计(这是一个粗略的数字).这比只判断符合条件的行要昂贵得多,而这正是我们希望从索引中获得的好处.

那么为什么会这样呢?

在搜索中对索引列使用任何函数时,如下所示:

WHERE
  DATE(created_at) BETWEEN '2022-09-09' AND '2022-09-12'

它 destruct 了索引减少判断行数的好处.

MySQL的优化器不知道函数的结果,因此它不能推断返回值的顺序与索引的顺序相同.因此,它不能利用索引已排序这一事实来缩小搜索范围.您和我都知道,DATE(created_at)created_at的顺序相同是很自然的,但查询优化器并不知道这一点.还有像MONTH(created_at)这样的其他函数,在这些函数中,结果按排序顺序肯定是not,并且MySQL的优化器不会try 知道哪个函数的结果是可靠排序的.

要修复您的查询,可以try 以下两种方法之一:

使用表达式索引.这是MySQL 8.0中的一个新功能:

ALTER TABLE `advert_requests` ADD INDEX ((DATE(created_at)))

请注意多余的一对括号.在定义表达式索引时,这些是必需的.索引项是该函数或表达式的结果,而不是列的原始值.

如果您随后在查询中使用相同的表达式,优化器会识别并使用索引.

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);

优化器识别该表达式的技巧仍然有效.

如果您使用的MySQL版本早于5.7,则无论如何都应进行升级.MySQL5.6和更早的版本现在已经过了它们的生命周期,它们存在安全风险.

您可以做的第二件事是重构您的查询,使created_at列不在函数中.

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.

Mysql相关问答推荐

MySQL InnoDB:可以在没有回滚损失的情况下从运行的查询中进行大型插入吗

计算符合字段值只能包含一种事件类型这一事实的记录

子查询是否可以按主查询中的列进行分组?

带有值分隔的MySQL分组不起作用

MySQL 8.0.33 Select json列时出现错误:排序内存不足,请考虑增加服务器排序缓冲区大小

如何确保 SQL 记录的列不引用它自己的主键?

为什么 `count` 函数有效但 `sum` 无效?

Mysql如何分行分词

完全匹配 IN 子句中的所有值

了解 Spring 和数据库级别的事务?

mysql 5.5;可以从记录中排除表吗?

如何在 Windows 上访问 xampp 的命令行

终止空闲的mysql连接

在mysql中将纪元数转换为人类可读的日期

MySql 以秒为单位的两个时间戳之间的差异?

#1146 - 表 'phpmyadmin.pma_recent' 不存在

MySQL 中的 VARCHAR(255) 和 TINYTEXT 字符串类型有什么区别?

将 MySQL 数据库置于版本控制之下?

我可以在单个 Amazon RDS 实例上创建多少个数据库

邮箱地址可接受的字段类型和大小?