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' 并且您有一个查询,您希望在其中找到所有打开状态。
您需要在此处定义一个包含特定列顺序的综合索引。一般的经验法则是:
所有在WHERE
子句内,由AND
子句连接,比较的列优先使用 =
、IS NULL
或 <=>
转换为常数值 。因此,在您的查询中,后面有两列:Data_Type
和 Status
.
次要优先考虑以下三种情况:
- 具有范围条件的列。
GROUP BY
子句中特定顺序的列(如果存在)。
ORDER BY
子句中特定顺序的列(如果存在)
在这种情况下,Created
是一个范围条件,所以我们将该列添加到末尾的索引中,因为在遇到范围条件时,MySQL 停在该列,并且无法访问索引中的更多列。
因此,您基本上需要定义以下索引:
ALTER TABLE Activities ADD INDEX(Data_Type, Status, Created);
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.
我 运行 在包含 ~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' 并且您有一个查询,您希望在其中找到所有打开状态。
您需要在此处定义一个包含特定列顺序的综合索引。一般的经验法则是:
所有在
WHERE
子句内,由AND
子句连接,比较的列优先使用=
、IS NULL
或<=>
转换为常数值 。因此,在您的查询中,后面有两列:Data_Type
和Status
.次要优先考虑以下三种情况:
- 具有范围条件的列。
GROUP BY
子句中特定顺序的列(如果存在)。ORDER BY
子句中特定顺序的列(如果存在)
在这种情况下,Created
是一个范围条件,所以我们将该列添加到末尾的索引中,因为在遇到范围条件时,MySQL 停在该列,并且无法访问索引中的更多列。
因此,您基本上需要定义以下索引:
ALTER TABLE Activities ADD INDEX(Data_Type, Status, Created);
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.