为啥索引会使查询变得非常慢?

Posted

技术标签:

【中文标题】为啥索引会使查询变得非常慢?【英文标题】:Why an index can make a query really slow?为什么索引会使查询变得非常慢? 【发布时间】:2011-04-16 12:04:25 【问题描述】:

有一天,我在 SO 上回答了 question(被接受为正确),但答案让我非常怀疑。 很快,用户就有了一个包含以下字段的表:

id INT PRIMARY KEY
dt DATETIME (with an INDEX)
lt DOUBLE

查询SELECT DATE(dt),AVG(lt) FROM table GROUP BY DATE(dt) 真的很慢。 我们告诉他(部分)问题在于使用 DATE(dt) 作为字段和分组,但 db 位于生产服务器上,无法拆分该字段。 因此(使用触发器)插入了另一个字段da DATE (with an INDEX),自动填充了 DATE(dt)。查询 SELECT da,AVG(lt) FROM table GROUP BY da 稍微快一点,但大约有 800 万条记录,它花了大约 60 秒!!! 我在我的电脑上试过,最后我发现,删除字段 da 查询上的索引只需要 7 秒,而删除索引后使用 DATE(dt) 需要 13 秒。 我一直认为用于分组的列上的索引可以真正加快查询速度,而不是相反(慢 8 倍!!!)。 为什么?是什么原因? 非常感谢。

【问题讨论】:

@FractalizeR:在我的电脑上我有 mysql5.5 【参考方案1】:

因为您仍然需要从索引 + 数据文件中读取所有数据。由于您没有使用任何 where 条件 - 您将始终拥有查询计划,该计划逐行访问所有数据,您对此无能为力。

如果性能对此查询很重要并且经常执行 - 我建议将结果缓存到某个临时表中并每小时(每天等)更新一次。

为什么变慢:因为在索引中数据已经排序,当mysql计算查询执行的成本时,它认为使用已经排序的数据会更好,然后对它进行分组,然后计算聚合。但在这种情况下并非如此。

【讨论】:

我不认为这可以解释为什么在删除索引后他的查询性能得到提升。 @zerkms: 但 MySql 必须以group by 为索引列,所以我认为聚合 close 记录应该更快。很明显,我认为不是现实中发生的事情 :) 但我想很好地理解它,因为我使用 MySql 并且将来我可能会对我们的用户犯同样的错误(也许直到我有很多记录) @FractalizeR:当他有一个索引时——mysql需要去索引来获取rowid(物理属性,数据文件中的偏移量)——之后去数据文件,读取那一行,然后返回索引等。取而代之的是,您可以按顺序读取数据文件。顺序读取比大量重复随机读取更快。这不是错误,而是预期的行为,由优化器计算的查询成本评估不准确引起的。 @Marco:即使它附近有“关闭”值(因为它们已排序) - mysql 仍然需要转到数据文件来计算聚合值。删除AVG(field) 并尝试查询:SELECT da FROM table GROUP BY da。它应该很快。 @FractalizeR:ps:是的,它看起来像那个错误,抱歉。但是根据那里的 cmets 是固定的:-S 好吧,我认为这种行为有相同的根源:错误的 CBO 计算。【参考方案2】:

我认为这是因为这个或类似的 MySQL 错误:Index degrades sort performance and optimizer does not honor IGNORE INDEX

【讨论】:

哇,我什至不知道,谢谢。无论如何,他们正在谈论 MySQL 5.1,他们告诉他们已经提交了一个补丁......【参考方案3】:

我记得这个问题,因为我正要回答它,但被别的东西分心了。问题是他的表设计没有利用聚集主键索引。

我会重新设计表,创建一个复合聚簇主键,其中日期作为索引的前导部分。 sm_id 字段仍然只是一个连续的 unsigned int 以保证唯一性。

drop table if exists speed_monitor;
create table speed_monitor 
(
created_date date not null,
sm_id int unsigned not null,
load_time_secs double(10,4) not null default 0,
primary key (created_date, sm_id)
)
engine=innodb;

+------+----------+
| year | count(*) |
+------+----------+
| 2009 | 22723200 | 22 million
| 2010 | 31536000 | 31 million
| 2011 |  5740800 |  5 million
+------+----------+

select 
 created_date, 
 count(*) as counter,
 avg(load_time_secs) as avg_load_time_secs
from
 speed_monitor
where
 created_date between '2010-01-01' and '2010-12-31'
group by
 created_date
order by
 created_date
limit 7;

-- cold runtime

+--------------+---------+--------------------+
| created_date | counter | avg_load_time_secs |
+--------------+---------+--------------------+
| 2010-01-01   |   86400 |         1.66546802 |
| 2010-01-02   |   86400 |         1.66662466 |
| 2010-01-03   |   86400 |         1.66081309 |
| 2010-01-04   |   86400 |         1.66582251 |
| 2010-01-05   |   86400 |         1.66522316 |
| 2010-01-06   |   86400 |         1.66859480 |
| 2010-01-07   |   86400 |         1.67320440 |
+--------------+---------+--------------------+
7 rows in set (0.23 sec)

【讨论】:

是的,但它的日期字段不是日期,而是日期时间,实际上他必须使用 DATE(...)。无论如何,在插入字段 da 之后,您会将 PK 声明为复合 (id,da) 吗?好吧,也许你是对的,但请相信我:如果 db 是我的,我不应该考虑这个。我会遇到与其他用户相同的问题,但不知道原因;) 好的,所以继续添加一个新的日期字段,但使该新字段成为集群复合键的一部分

以上是关于为啥索引会使查询变得非常慢?的主要内容,如果未能解决你的问题,请参考以下文章

为啥临时表会使这个查询变得如此之快?

在mysql中按查询排序变得非常慢

为啥这个 mySQL 查询非常慢?

为啥这个 UPDATE 查询会杀死我的 CPU?

将 charset 设置为 utf8mb4 会使查询非常慢

SQL Server:存储过程变得非常慢,原始 SQL 查询仍然非常快