为啥从大表中查询 COUNT() 比 SUM() 快得多

Posted

技术标签:

【中文标题】为啥从大表中查询 COUNT() 比 SUM() 快得多【英文标题】:Why is COUNT() query from large table much faster than SUM()为什么从大表中查询 COUNT() 比 SUM() 快得多 【发布时间】:2013-06-17 13:34:03 【问题描述】:

我有一个包含以下表格的数据仓库:

主要

大约 800 万条记录

CREATE TABLE `main` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`cid` mediumint(8) unsigned DEFAULT NULL, //This is the customer id
`iid` mediumint(8) unsigned DEFAULT NULL, //This is the item id
`pid` tinyint(3) unsigned DEFAULT NULL, //This is the period id
`qty` double DEFAULT NULL,
`sales` double DEFAULT NULL,
`gm` double DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_pci` (`pid`,`cid`,`iid`) USING HASH,
KEY `idx_pic` (`pid`,`iid`,`cid`) USING HASH
) ENGINE=InnoDB AUTO_INCREMENT=7978349 DEFAULT CHARSET=latin1

期间

这张表大约有50条记录,有以下字段

身份证 月 年

客户

这有大约 23,000 条记录和以下文件

身份证 number //这个字段是唯一的 name //这只是一个描述字段

以下查询运行速度非常快(不到 1 秒)并返回大约 2,000:

select count(*) 
from mydb.main m 
INNER JOIN mydb.period p ON p.id = m.pid 
INNER JOIN mydb.customer c ON c.id = m.cid 
WHERE p.year = 2013 AND c.number = 'ABC';

但是这个查询要慢得多(超过 45 秒),和前面的一样,只是求和而不是计数:

select sum(sales)
from mydb.main m 
INNER JOIN mydb.period p ON p.id = m.pid 
INNER JOIN mydb.customer c ON c.id = m.cid 
WHERE p.year = 2013 AND c.number = 'ABC';

当我解释每个查询时,我看到的唯一区别是'count()' 查询“Extra”字段为“使用索引”,而对于“sum()”查询,该字段为 NULL。

解释 count() 查询

| id | select_type | table | type  | possible_keys        | key          | key_len | ref                 | rows | Extra       |
|  1 | SIMPLE      | c     | const | PRIMARY,idx_customer | idx_customer | 11      | const               |    1 | Using index |
|  1 | SIMPLE      | p     | ref   | PRIMARY,idx_period   | idx_period   | 4       | const               |    6 | Using index |
|  1 | SIMPLE      | m     | ref   | idx_pci,idx_pic      | idx_pci      | 6       | mydb.p.id,const     |    7 | Using index |

解释 sum() 查询

| id | select_type | table | type  | possible_keys        | key          | key_len | ref                 | rows | Extra       |
|  1 | SIMPLE      | c     | const | PRIMARY,idx_customer | idx_customer | 11      | const               |    1 | Using index |
|  1 | SIMPLE      | p     | ref   | PRIMARY,idx_period   | idx_period   | 4       | const               |    6 | Using index |
|  1 | SIMPLE      | m     | ref   | idx_pci,idx_pic      | idx_pci      | 6       | mydb.p.id,const     |    7 | NULL        |
为什么 count() 比 sum() 快这么多?不应该对两者都使用索引吗? 如何使 sum() 更快?

提前致谢!

编辑

所有表都显示它正在使用 Engine InnoDB

另外,附带说明一下,如果我只是执行“SELECT *”查询,它会运行得非常快(不到 2 秒)。我希望 'SUM()' 不应该花费比这更长的时间,因为 SELECT * 无论如何都必须检索行......

已解决

这是我学到的:

由于销售字段不是索引的一部分,它必须从硬盘驱动器中检索记录(这可能有点慢)。 我对此不太熟悉,但看起来 I/O 性能可以通过切换到 SSD(固态驱动器)来提高。我将不得不对此进行更多研究。 现在,我想我要创建另一层摘要以获得我正在寻找的性能。 我将主表上的索引重新定义为 (pid,cid,iid,sales,gm,qty),现在 sum() 查询运行得非常快!

谢谢大家!

【问题讨论】:

没有关于销售的索引,所以除了访问行指针之外,引擎必须读取硬盘驱动器上的相应行以找到每条记录的值并将它们相加......计数操作要快得多,它是 100% 缓冲的。 此外,在 period 上选择的索引看起来可能只是在 year 列上,而不是 year 和 pid 上的覆盖索引(这将允许在连接上使用索引)。跨度> 【参考方案1】:

索引是关键行的列表。

当您执行count() 查询时,可以忽略数据库中的实际数据,只使用索引。

当您执行sum(sales) 查询时,必须从磁盘读取每一行以获取销售数据,因此要慢得多。

此外,可以批量读取索引,然后在内存中进行处理,而磁盘访问将随机破坏驱动器,试图从磁盘中读取行。

最后,索引本身可能包含计数摘要(以帮助生成计划)

更新

实际上,您的表上有三个索引:

PRIMARY KEY (`id`),
KEY `idx_pci` (`pid`,`cid`,`iid`) USING HASH,
KEY `idx_pic` (`pid`,`iid`,`cid`) USING HASH

所以您只有在 idpidcidiid 列上有索引。 (顺便说一句,大多数数据库都足够智能,可以组合索引,因此您可能可以对索引进行一些优化)

如果您添加了另一个键,例如 KEY idx_sales(id,sales)可以提高性能,但考虑到销售价值的数字分布,您会为更新增加额外的性能成本,这可能是一件坏事

【讨论】:

可能是桶。 MyISAM 表保持总数,但 InnoDB(他正在使用)没有。 好吧,有道理,有什么办法可以让 sum() 更快吗? 另外,当使用计数时,像 COUNT(1) 一样包装它,因为你不需要计算所有的列。 请看我的编辑。如果 sum(sales) 很慢,因为它必须从磁盘读取,为什么 select * 很快?那不是也必须从磁盘读取吗? SELECT * FROM mydb.main 没有应用任何排序,因此数据库可以使用磁盘顺序返回行,这意味着它可以直接从磁盘流式传输。如果您应用的排序会减慢一些速度,然后选择返回更多数据,那么可以将延迟归咎于客户端无法跟上!【参考方案2】:

简单的答案是count() 只计算行数。这可以通过索引来满足。

sum() 需要识别每一行然后获取页面以获取sales 列。这会增加很多开销——每行大约加载一个页面。

如果将sales 添加到索引中,那么它也应该很快,因为它不必获取原始数据。

【讨论】:

以上是关于为啥从大表中查询 COUNT() 比 SUM() 快得多的主要内容,如果未能解决你的问题,请参考以下文章

优化从大表中选择

如何优化限制查询以更快地从大表中访问数据?

MySQL查询优化从大表中获取8-10条记录

从大表中选择非空字段

SQL怎么从大表里面 查询包含小表的内容?

MySQL nodejs 在从大表中选择数据时崩溃