我应该数(*)还是不数?

我知道做这样的查询通常不是个好主意:

SELECT * FROM `group_relations`

但是当我只需要计数时,我是否应该使用这个查询,因为它允许更改表,但仍然会产生相同的结果。

SELECT COUNT(*) FROM `group_relations`

或者更具体一点

SELECT COUNT(`group_id`) FROM `group_relations`

我有一种感觉,后者可能会更快,但还有其他事情要考虑吗?

更新 : 本例中我使用的是 InnoDB,抱歉没有具体说明。

10175 次浏览

如果我没记错的话,在 MYSQL 中,COUNT (*)计算所有行,而 COUNT (column _ name)只计算给定列中具有非 NULL 值的行。

如果所涉及的列为 NOT NULL,则两个查询是等效的,

select count(*)

将计算所有行,而

select count(group_id)

将只计算 group _ id 不为空的行数。

此外,一些数据库系统,如 MySQL,在请求 count (*)时使用了一种优化,这使得这种查询比特定的查询稍微快一些。

就个人而言,当只是计数时,我做 count (*)是为了保险起见。

如果尝试 SELECT COUNT(1) FROMgroup _ relations,速度会快一些,因为它不会尝试从列中检索信息。

编辑: 我刚刚做了一些研究,发现这只发生在一些数据库。在 sqlserver 中使用1或 * 是相同的,但是在 Oracle 中使用1更快。

Http://social.msdn.microsoft.com/forums/en-us/transactsql/thread/9367c580-087a-4fc1-bf88-91a51a4ee018/

显然在 mysql 中它们之间没有区别,就像解析器将查询更改为 select (1)一样。如果我在某些方面误导了你,我很抱歉。

它应该取决于你实际上正在努力实现的,正如塞巴斯蒂安已经说过的,也就是说,让你的意图清楚!如果 只是计算行数,那么执行 COUNT (*) ,或者执行 COUNT (列)。

这可能是值得检查您的数据库供应商了。回到我使用 Informix 的时候,它对 COUNT (*)进行了优化,与计算单列或多列相比,COUNT (*)的查询计划执行成本为1,这将导致更高的数字

COUNT (*)计算所有行数,而 COUNT (column _ name)将只计算指定列中没有 NULL 值的行数。

在 MySQL 中需要注意:

对于 * 或非空列,在 MyISAM 表上 COUNT ()非常快,因为行计数是缓存的。InnoDB 没有行计数缓存,所以不管列是否为 null,COUNT (*)或 COUNT (column _ name)的性能没有差异。您可以在 MySQL 性能博客上了解更多关于 这篇文章的差异。

MySQLISAM 表应该对 COUNT (*)进行优化,跳过全表扫描。

如果尝试 SELECT COUNT (1) FROM group _ relations,速度会快一些,因为它不会尝试从列中检索信息。

COUNT (1)过去比 COUNT (*)快,但现在不再是这样了,因为现代 DBMS 足够聪明,知道您不想了解列

在选择表的所有字段时,COUNT 中的星号与星号没有关系。说 COUNT (*)比 COUNT (field)慢完全是胡说八道

我的直觉告诉我,选择 COUNT (*)比选择 COUNT (field)要快。如果 RDBMS 检测到您在 COUNT 上而不是字段上指定了“ *”,那么它不需要计算任何东西来递增计数。然而,如果在 COUNT 上指定字段,则 RDBMS 将始终计算字段是否为 null 或不计算它。

但是如果您的字段可以为空,请在 COUNT 中指定该字段。

数数事实和神话:

谬论 : “ InnoDB 不能很好地处理 count (*)查询”:

如果有 WHERE 子句,大多数 count (*)查询由所有存储引擎以相同的方式执行,否则 InnoDB 将不得不执行完整的表扫描。

FACT : InnoDB 不优化没有 where 子句的 count (*)查询

我自己也很好奇。阅读文档和理论上的答案没有问题,但我喜欢在这两者之间找到平衡经验证明。

我有一个 MySQL 表(InnoDB) ,其中有5,607,997条记录。该表位于我自己的私有沙箱中,所以我知道内容是静态的,没有其他人在使用服务器。我认为这有效地消除了对性能的所有外部影响。我有一个自动增量主键字段(Id)的表,我知道这个字段永远不会为空,我将在 WHERE 子句测试中使用这个字段(WHERE Id 不为空)。

在运行测试时,我看到的唯一可能的其他故障是缓存。第一次运行查询的速度总是慢于使用相同索引的后续查询。我将在下面将其称为缓存种子调用。为了混淆一下,我使用 where 子句运行它,我知道无论任何数据(TRUE = TRUE) ,它的值总是为 TRUE。

以下是我的研究结果:

QueryType

      |  w/o WHERE          | where id is not null |  where true=true

计数()

      |  9 min 30.13 sec ++ | 6 min 16.68 sec ++   | 2 min 21.80 sec ++
|  6 min 13.34 sec    | 1 min 36.02 sec      | 2 min 0.11 sec
|  6 min 10.06 se     | 1 min 33.47 sec      | 1 min 50.54 sec

计数(身份证)

      |  5 min 59.87 sec    | 1 min 34.47 sec      | 2 min 3.96 sec
|  5 min 44.95 sec    | 1 min 13.09 sec      | 2 min 6.48 sec

点数(1)

      | 6 min 49.64 sec    | 2 min 0.80 sec       | 2 min 11.64 sec
| 6 min 31.64 sec    | 1 min 41.19 sec      | 1 min 43.51 sec

+ + 这被认为是缓存种子调用。它应该比其他调用慢。

我觉得结果不言而喻。计数(Id)通常优于其他的。添加 Where 子句可以显著减少访问时间,即使它是一个您知道将被评估为 true 的子句。最佳位置似乎是 COUNT (Id) ... 其中 Id 不为空。

我希望看到其他人的结果,也许是使用较小的表格,或者使用针对不同字段的子句,而不是你正在计算的字段。我肯定还有其他的变化我没有考虑到。

我从 MySQL 得到的关于这类事情的建议是,总的来说,试图基于这样的技巧来优化查询从长远来看可能是一个诅咒。在 MySQL 的历史中有一些例子,其中某些人依赖于优化器如何工作的高性能技术最终成为下一个版本的瓶颈。

编写回答所提问题的查询——如果需要对所有行进行计数,请使用 COUNT (*)。如果需要非空列的计数,请使用 COUNT (col) WHERE colIs NOT NULL。适当地索引,并将优化留给优化器。尝试进行自己的查询级别优化有时会降低内置优化器的效率。

也就是说,您可以在查询中做一些事情来使优化器更容易地加速它,但是我不认为 COUNT 是其中之一。

编辑: 尽管如此,上面答案中的统计数据还是很有趣的。在这种情况下,我不确定优化器中是否真的有什么东西在起作用。我只是在讨论一般的查询级别优化。

最好通过索引列(如主键)进行计数。

SELECT COUNT(`group_id`) FROM `group_relations`

我知道这不是个好主意 这样的疑问:

SELECT * FROM `group_relations`

但是当我只想要伯爵的时候,应该 我选择这个查询,因为它允许 这张表可以改变,但仍然有效 同样的结果。

SELECT COUNT(*) FROM `group_relations`

正如您的问题所暗示的,SELECT *不明智的原因是对表的更改可能需要对代码进行更改。这不适用于 COUNT(*)。想要获得 SELECT COUNT('group_id')提供的特殊行为是非常罕见的——通常您想要知道记录的数量。这就是 COUNT(*)的作用,所以要好好利用它。

寻找替代品

如您所见,当表变大时,COUNT查询会变慢。我认为最重要的是要考虑你要解决的问题的性质。例如,许多开发人员在为大型记录集生成分页时使用 COUNT查询,以确定结果集中的总页数。

知道 COUNT查询将增长缓慢,您可以考虑另一种显示分页控件的方法,这种方法只允许您绕过缓慢的查询。谷歌的分页就是一个很好的例子。

去常态化

如果您绝对必须知道与特定计数相匹配的记录数,请考虑经典的数据反规范化技术。不要在查找时计算行数,而是考虑在记录插入时递增一个计数器,在删除记录时递减该计数器。

如果您决定这样做,请考虑使用幂等事务操作来保持这些非规范化值的同步。

BEGIN TRANSACTION;
INSERT INTO  `group_relations` (`group_id`) VALUES (1);
UPDATE `group_relations_count` SET `count` = `count` + 1;
COMMIT;

或者,如果您的 RDBMS 支持数据库触发器,您也可以使用它们。

根据您的体系结构,使用 memcached 这样的缓存层来存储、递增和递减非规范化值,并在缓存键丢失时直接进入缓慢的 COUNT 查询,这可能是有意义的。如果您的数据非常不稳定,这可以减少总体的写争用,不过在这种情况下,可以考虑使用 狗堆效应的解决方案