使用COUNT(*)或SELECT *是一个好主意吗?

8

我听说过几次,由于性能原因,不应执行COUNT(*)SELECT *,但无法找到更多信息。

我可以想象数据库正在使用所有列进行操作,这可能会导致令人印象深刻的性能损失,但我不确定。有人对这个主题有进一步的信息吗?


5
一些数据库引擎会通过使用索引来优化COUNT(*)的操作,因为它们会忽略列中的实际值。 - Gabe
有趣的文章 - Frederik Gheysels
1
我永远不会在“存储过程”中使用SELECT *。很多时候,我必须弄清楚为什么我的代码出错了,原因是数据库现在返回了一个新列。 - Neil Knight
@Gabe:几乎正确:...通过使用PK,因为它会有所不同! - iDevlop
8个回答

15

1. 在 count(*) 和 count(something else) 之间的选择

SQL 是一种声明式语言,你在其中指定 你想要什么。这与指定 如何 获得所需不同。这意味着数据库引擎可以自由地以它认为最高效的方式实现你的查询。许多数据库优化器会将你的查询重写为代价更小的替代方案(如果有这样的计划)。

假设有以下表:

table(
   pk       not null
  ,color    not null
  ,nullable null
  ,unique(pk)
  ,index(color)
);

...所有以下内容在功能上是等价的(由于countnulls的机制):

1) select count(*) from table;
2) select count(1) from table;
3) select count(pk) from table;
4) select count(color) from table;
无论使用哪种形式,如果查询优化器认为另一种形式更高效,它可以自由地将查询重写为另一种形式(但并非所有优化器都足够复杂以执行此操作)。唯一索引(pk)的占用空间比整个表要小,因此计算索引条目数量比扫描整个表更有效。在Oracle中,我们有位图索引,它还可以压缩重复字符串。如果我们在颜色列上使用了这样的索引,那么它可能是扫描最小的索引。Oracle还支持表压缩,在某些情况下,物理表比复合索引还要小。
1. 简而言之,您特定的数据库管理系统将拥有其自己的一套工具,可以启用不同的重写规则和执行计划。这使得问题有些毫无意义(除非我们谈论特定数据库管理系统的特定版本)。我建议在所有情况下使用COUNT(*),因为它需要最少的认知努力来理解。 2. 在您编写并投入生产的代码中,SELECT * 的有效用途非常少。想象一张包含蓝光电影(是的,电影以 blob 的形式存储在此表中)的表。所以你将自己粘贴起来的神奇抽象层和SELECT * FROM movies where id = ?放到了getMovies(movie_id)方法中。我将克制自己不解释为什么SELECT name FROM movies会在网络上传输得更快一些。当然,在大多数现实情况下,这不会有明显的影响。 关于性能的最后一点是,当查询中的所有引用列(被选择的、过滤的)都存在于索引中(称为覆盖索引)时,数据库根本不需要触及表。它可以完全从仅扫描索引来解析。通过选择所有列,您从优化器中删除了此选项。 另一个关于SELECT *的事情比任何事情都更严重,那就是它会创建对表的特定物理布局的隐式依赖。让我解释一下。考虑以下表:
table T1(name, id)
table T2(name, id)
以下语句将从数组arr中删除所有等于val的元素,并返回新数组的长度:
``` arr = arr.filter(item => item !== val); return arr.length; ```
insert into t1 select * from t2;

如果发生以下任何情况,... 将会中断或产生不同的结果:

  • 表格列被重新排列,例如T1(id,name)
  • T1添加了另一个非空列
  • T2添加了另一列

2. 简短概述; 在可能的情况下,明确指定您想要的列(最终,您无论如何都必须这样做)。此外,选择较少的列比选择更多的列更快。显式选择的积极影响是它给予优化器更大的自由度。


“insert into t1 select * from t2” 在我看来实际上是一个很好的使用 select * 的例子,前提是这两个表因为其他原因需要格式相同。你的“破坏条件”应该是“T1获得一个非空列而T2没有”,以及“T2获得另一列而T1没有”。它将正确处理同时向两个表添加相同列的情况,而明确列出要使用的列的代码则会出错。 - supercat

6

COUNT(*)COUNT(column1)是不同的!
COUNT(*)返回记录数,不会使用更多资源,而COUNT(column1)计算column1非空记录数。

对于SELECT语句,情况不同。使用SELECT *会请求更多数据。


3
当使用count(*)时,*并不意味着“所有字段”。使用count(field)将统计字段中所有非空值的数量,但是count(*)将始终计算所有记录的数量,即使所有记录中的所有字段都为空,因此它根本不需要检查字段中的数据。
使用select *意味着您几乎总是返回比您要使用的更多的数据,这当然是一种浪费。然而,更为严重的是维护问题;如果您向表中添加字段,则查询也会返回这些字段。这可能意味着记录变得太大而无法适应缓冲区,导致错误消息。

2
不要将 "COUNT(*)" 中的 * 与 "SELECT *" 中的 * 混淆。它们完全没有关联,但有时会因为这种奇怪的语法而混淆。使用 COUNT(*) 没有任何问题,它只是表示“计算行数”。
另一方面,SELECT * 表示“选择所有列”。这通常是不好的做法,因为它将您的代码紧密耦合到数据库模式中。这意味着当您更改表时,即使应该不受影响,您也可能不得不更改代码。它增加了任何模式更改的影响。
SELECT * 还可能导致子优化查询计划。这要么是因为您实际上并不需要所有列,要么是因为它强制 DBMS 在运行时进行额外的查找以获取列列表。

0

根据数据库的大小,它变得越来越低效。最简单的描述方式如下:

当您特定执行以下操作时:

SELECT column1,column2,column3 FROM table1

Mysql 知道它正在寻找哪些列,但当你执行时
SELECT * FROM table1

MySQL 不知道你想要哪些列,它只知道你想要全部列但不知道列名,因此必须执行额外的任务来分析表以发现列,从而使用资源。


0

“*”代表“所有列”是绝对正确的。如果你有一张拥有大量列(比如100+)的表,这种查询在效率方面可能会很差。

我认为最好的解决方案是先创建数据库视图,预先过滤掉计数操作中涉及的记录数量,这样性能影响就不是一个大问题了,因为视图可以被缓存。

另一方面,似乎应该避免使用“*”运算符返回记录,最好选择你真正需要在业务中使用的字段。


0

使用 SELECT * 语句可能会影响性能。当应用程序实际上只需要少量列时,使用 SELECT * 语法的应用程序会在网络上传输比它们需要消耗的更多的数据,这是浪费的。

此外,在至少 Microsoft SQL Server 中,当您在视图中使用 SELECT *,然后向基础表添加列时,视图返回的列标题和数据不匹配!有关此特定问题的详细信息,请参见 我的博客文章


0

在使用COUNT(*)时,情况取决于数据库及其版本。例如,在现代版本的MS SQL中,它并不重要[需要来源]。

因此,对于COUNT(*),最好的方法是测量它。

使用SELECT *是一个非常糟糕的想法。*意味着读取所有列,这可能会产生沉重的IO和网络操作(特别是对于各种类型的CHAR列)。此外--你很少需要所有的列。


7
提示:如果你强调几乎每一个单词,它的意义就会丧失。 - Guffa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接