Mysql:相同的查询,相同数量的结果行,没有连接,慢 10 倍?

Mysql: same query, same number of resulting rows, no joins, 10x slower?

我 运行 在包含 ~3M 行的 innodb table 上执行以下两个查询。出于某种原因,第一个查询 return 168,199 行用了不到一秒,而第二个查询用了 8 秒 returns 167,159 行? return 几乎相同的结果所需的时间增加了 10 倍?

SELECT count(idActivities) as amt 
FROM Activities 
WHERE Data_Type='email' 
      AND Status='sent' 
      AND (Created > '2019-07-17 00:00:00' 
           AND Created <= '2019-08-17 00:00:00');

SELECT count(idActivities) as amt 
FROM Activities 
WHERE Data_Type='email' 
      AND Status='sent' 
      AND (Created > '2019-08-17 00:00:00' 
           AND Created <= '2019-09-17 00:00:00');

如果我从第二个语句中删除额外的 where 子句,例如

SELECT count(idActivities) as amt 
FROM Activities 
WHERE (Created > '2019-08-17 00:00:00' 
       AND Created <= '2019-09-17 00:00:00');

查询时间缩短为半秒。如果我在语句中添加一个附加的 where 子句,例如Data_Type= 或 Status= 跳回到 8 加秒。

我也试过将日期向任一方向移动几天,但这不会影响查询时间。

table 在 idActivities、Data_Type、Status 和 Created 上建立了索引。

服务器是 运行 5GB Ram,8 核,innodb_buffer_pool_size=3G,InnoDB 缓冲区使用率为 49%。

我在不同的服务器上尝试了相同的查询,结果在大约 4 秒左右的时间大致相同,这仍然很慢。

我注意到的唯一区别是 Data_Type 列的基数不同,即使 table 几乎相同(它是前一天的备份)。

如果能帮助我了解如何缩短查询时间,我将不胜感激? 运行 "DISTINCT(Data_Type)" 只有 return 整个 table 的 13 行。

已编辑 谢谢 Salman A,通过添加以下复合索引极大地提高了两个查询的性能:

创建索引 ix_1 ON 活动(已创建,Data_Type,状态);

我建议创建以下覆盖索引:

 CREATE INDEX ix_1 ON t (Data_Type, Status, Created)

列的顺序很重要。高基数列通常放在最前面,但是对于这个特定的查询,您需要将创建的列放在最后,因为它涉及范围比较(前两个需要相等比较)。

当你有这样的意外行为时,我总是通过让服务器解释它在做什么来检查服务器在做什么。

您可以在查询开始时使用 EXPLAIN 关键字来执行此操作。我猜较慢的查询正在扫描整个 table 以获取结果集

https://dev.mysql.com/doc/refman/8.0/en/using-explain.html

旁注:除非您的 data_type 和状态列非常有选择性(我猜他们不会)。我猜测它们是毫无意义的索引。通常情况下,只有在一种状态类型占很大比例时,索引状态列才是一个好主意。即 95% 已关闭 'status' 和 5% 'open' 并且您有一个查询,您希望在其中找到所有打开状态。

您需要在此处定义一个包含特定列顺序的综合索引。一般的经验法则是:

  1. 所有在WHERE子句内,由AND子句连接,比较的列优先使用 =IS NULL<=> 转换为常数值 。因此,在您的查询中,后面有两列:Data_TypeStatus.

  2. 次要优先考虑以下三种情况:

    • 具有范围条件的列。
    • GROUP BY 子句中特定顺序的列(如果存在)。
    • ORDER BY 子句中特定顺序的列(如果存在)

在这种情况下,Created 是一个范围条件,所以我们将该列添加到末尾的索引中,因为在遇到范围条件时,MySQL 停在该列,并且无法访问索引中的更多列。

因此,您基本上需要定义以下索引:

ALTER TABLE Activities ADD INDEX(Data_Type, Status, Created);

引用自Rick James' notes

When you have a composite index that could be in any order, the cardinality of the individual columns does not matter in picking the order. The cardinality of the entire index is what matters.