Count(*)和Count(1)的区别 - SQL Server

869

请问你们中是否有人使用 Count(1) 而不是 Count(*),是否会在性能上有明显差异?还是这只是一个从过去流传下来的遗留习惯?

具体的数据库是 SQL Server 2005


9
不清楚SQL Server,但在MySQL中没有区别。另一方面,COUNT(column)则是不同的。 - Greg
147
不正确。COUNT(SomeColumn)仅返回SomeColumn列中非空值的行数。COUNT(*)和COUNT('Foo')将返回表中所有行的总数。 - Steve Broberg
1
有关“select count 1 vs select count * in detail with graph”的详细信息,请查看此链接:http://advancesharp.com/Questions/160/sql-server---select-count1-vs-select-count - Ali Adravi
5
哇,史蒂夫,我已经学习了 5 年的 TSQL,却不知道 count(*) 和 Count(ColumnName) 的区别。谢谢。 - Harindaka
13个回答

681

没有区别。

原因:

在线书籍说“COUNT ( { [ [ ALL | DISTINCT ] expression ] | * } )

“1”是非空表达式:所以它与COUNT(*)相同。 优化器将其识别为微不足道的东西。

EXISTS (SELECT * ...EXISTS (SELECT 1 ...相同

示例:

SELECT COUNT(1) FROM dbo.tab800krows
SELECT COUNT(1),FKID FROM dbo.tab800krows GROUP BY FKID

SELECT COUNT(*) FROM dbo.tab800krows
SELECT COUNT(*),FKID FROM dbo.tab800krows GROUP BY FKID

同样的IO,同样的计划,一切都有效

编辑,2011年8月

在DBA.SE上有类似的问题

编辑,2011年12月

COUNT(*)ANSI-92中特别提到(查找“标量表达式125”)

情况:

a)如果指定了COUNT(*),则结果是T的基数。

也就是说,ANSI标准认为你的意思是显而易见的。由于这种迷信,RDBMS供应商已经将COUNT(1)优化掉了。否则,它将按照ANSI进行评估

b)否则,让TX成为单列表,该表是将应用于T的每一行并消除空值的结果。如果消除了一个或多个空值,则会引发完成条件:警告-


94

我在SQL Server团队工作,希望能在这个帖子中澄清一些问题(我之前没有看到过,所以很抱歉工程团队之前没有这样做)。

简而言之:

select count(1) from tableselect count(*) from table之间没有语义上的区别,也没有明显的性能差异。

解释:

首先,select count(1) from tableselect count(*) from table之间没有语义上的区别。它们在所有情况下返回相同的结果(如果不是这样,则是一个错误)。正如其他答案中所指出的,select count(column) from table在语义上有所不同,并且并不总是返回与count(*)相同的结果。

其次,就性能而言,在SQL Server(和SQL Azure)中有两个方面需要考虑:编译时间工作和执行时间工作。在当前实现中,编译时间工作是一项微不足道的额外工作。在某些情况下,会将“*”扩展到所有列,然后由于绑定和优化中的某些内部操作方式,再将输出减少到1列。我怀疑它不会在任何可测量的测试中显示出来,并且很可能会在其他发生在幕后的事情(例如自动统计信息、xevent会话、查询存储开销、触发器等)的噪音中丢失。这可能只是多出了几千个CPU指令。因此,在编译期间,count(1)做的工作要少一点(通常只会发生一次,计划会在多个后续执行中缓存)。对于执行时间而言,假设计划相同,则应该没有可测量的差异。(早期的一个例子显示出了差异——如果计划相同,则最有可能是由于机器上的其他因素导致的。)
免责声明:计划如何可能不同(非常罕见)。

这些情况发生的可能性极小,但在当前优化器的架构中有潜在的可能性。

SQL Server的优化器作为一个搜索程序(类比于计算机程序下棋,搜索各种不同查询部分的替代方案并计算出最便宜的计划),该搜索有一些限制,以保持查询编译在合理的时间内完成。对于超过最简单的查询,搜索有几个阶段,并且根据优化器认为查询的成本如何来处理查询的交易。

有三个主要的搜索阶段,每个阶段都可以运行更具攻击性(昂贵)的启发式算法,尝试找到比先前任何解决方案更便宜的计划。最终,在每个阶段结束时,会有一个决策过程,试图确定是否应该返回迄今为止找到的计划,还是应该继续搜索。

此过程使用迄今为止所花费的总时间与迄今为止找到的最佳计划的估计成本进行比较。因此,在不同速度的CPU的不同机器上,由于在早期阶段计划超时而导致计划与进入下一个搜索阶段不同,这是可能的(虽然很少见)。

还有一些与最后阶段超时和非常消耗机器全部内存的昂贵查询相关的相似场景(在64位上通常不是问题,但在32位服务器上这是一个更大的问题)。

最终,如果您得到了一个不同的计划,则运行时性能将有所不同。我认为编译时间的差异永远不可能导致发生这些情况。

总而言之:

请使用您想要的任何一种,因为这些都不会以任何实际形式影响表现。(诚实地说,在 SQL 中影响性能的因素远远超出了这个主题。)

更多信息

希望这能有所帮助。我确实写过一章关于优化器是如何工作的书籍,但我不知道在这里张贴是否合适(因为我仍然相信会从中获得微薄的版税)。因此,我会发布一个链接,让您可以查看我在英国 SQLBits 上关于优化器高级搜索各个主要阶段的演讲,以便您更详细地了解它们。

这是相关视频链接: 深入了解 SQL Server 查询优化器

3
我认为1也经历了同样的扩展。我基于这个性能测试 https://dev59.com/VXI-5IYBdhLWcg3w9tdw#6140367,并且在那个答案中还有一个查询示例,当列级别权限处于活动状态时,使用`1`的查询会出现意外错误。 - Martin Smith
@conor 很高兴听到你的回答是“从内部”,尽管我的问题是关于PostgreSQL的,我怀疑它仍然适用!你有视频的更新链接吗?在Chrome或Firefox上无法播放。 - Ricardo
好的,懒惰的我,在视频分享平台上找到了这个视频:深入了解 SQL Server 查询优化器 - Ricardo

82
在 SQL Server 中,这些语句会生成相同的执行计划。与流行观点相反,在 Oracle 中它们也是如此。在 Oracle 中,SYS_GUID() 是一个计算密集型函数。在我的测试数据库中,t_even 是一个有 1,000,000 行的表。这个查询:
SELECT  COUNT(SYS_GUID())
FROM    t_even

这段代码运行了48秒,因为函数需要评估每个返回的SYS_GUID()以确保它不是NULL

然而,这个查询:

SELECT  COUNT(*)
FROM    (
        SELECT  SYS_GUID()
        FROM    t_even
        )

这段代码运行了2秒,因为它甚至没有尝试评估SYS_GUID()(尽管*COUNT(*)的参数)。


@asgs:你为什么这样认为?COUNT(*) 如何依赖于 SYS_GUID 的值? - Quassnoi
既然你问了,我也不确定。我认为对于 COUNT(*) 来运行,它需要一个表格,因此子查询应该像一个表格一样工作。否则,我不认为 COUNT(*) 会返回一个有意义的值。 - asgs
@asgs:不一定,大多数编程语言都会对 a && b 进行优化,如果 a 为 false,则不会去评估 b。这个原则在这里同样适用。 - Quassnoi
2
@asgs:假设您知道map方法的作用,您是否看到这两个表达式:t_even.map(() => sys_guid()).lengtht_even.length始终返回相同的值?Oracle的优化器足够聪明,可以看到并优化掉map部分。 - Quassnoi
1
@asgs 没错。只是一个小修正:length 不完全取决于集合包含的内容,而是取决于其元素数量。如果这个数字存储在集合的元数据中(这不适用于 Oracle 或大多数其他现代 RDBMS,但适用于旧版 MySQL 的存储引擎 MyISAM),那么 COUNT(*) 只需要从元数据中获取值即可。 - Quassnoi
显示剩余5条评论

78

显然,COUNT(*)COUNT(1)总是返回相同的结果。因此,如果其中一个比另一个慢,那么这实际上是由于优化器中存在错误。由于这两种形式在查询中使用非常频繁,因此数据库管理系统不允许这种错误存在而不予修复。因此,你会发现在所有主要的SQL数据库管理系统中,这两种形式的性能(可能)是相同的。


6
如果count(1)比count(*)慢,我不会认为这是一个bug。如果您要求dbms生成1并计算那些不为空的数量,那么确实归结为记录计数,但您不能期望dbms检测出您编写的每个无意义的内容,并为您绕过它。 - Thorsten Kettner
5
优化器的作用是进行优化,在计数时只需要考虑两种情况:可能为空的表达式和永远不会为空的表达式。count(1) 属于后者,因此数据库管理系统无需“生成” 1 来回答该问题。(顺便说一句,出于审美原因,我永远不会使用除 count(*) 之外的任何内容。) - Tony Andrews

24
在SQL-92标准中,COUNT(*)具体意思是“表达式的基数”(可以是基本表、视图、派生表、CTE等)。
我猜想的是,这个想法是因为COUNT(*)容易解析。如果使用任何其他表达式,则需要解析器确保它不引用任何列(例如a是文字的情况下的 COUNT('a')a是列的情况下的COUNT(a) 可能会产生不同的结果)。
同样,在熟悉SQL标准的人类编码者看来,COUNT(*)也可以很容易地被挑选出来,这是在与多个供应商的SQL提供内容一起工作时非常有用的技能。
此外,在特殊情况下的SELECT COUNT(*) FROM MyPersistedTable;,认为DBMS很可能保存了表的基数统计信息。
因此,由于COUNT(1)COUNT(*)在语义上是等效的,所以我使用COUNT(*)

1
SQL-92文本链接自我的DBA.SE答案:http://dba.stackexchange.com/questions/2511/what-is-the-difference-between-select-count-and-select-countany-non-null-col/2512#2512 - gbn

20

COUNT(*)COUNT(1)在结果和性能方面是相同的。


15

我希望优化器可以确保在奇怪的边缘情况之外没有真正的差异。

就像任何事情一样,唯一真正的方法是测量您特定的情况。

话虽如此,我一直使用COUNT(*)


根据被接受的答案,这对于MS SQL来说并不是真的 - 实际上两者之间没有区别。 - David Manheim

14

由于这个问题一再出现,我在这里提供一个答案。我希望能为初学者们提供一些关于“最佳实践”的建议。

SELECT COUNT(*) FROM something 统计记录数,这是一项容易的任务。

SELECT COUNT(1) FROM something 检索每条记录的1并统计非空的1的数量,本质上也是统计记录数,只不过更加复杂。

尽管如此:好的数据库管理系统会注意到第二个语句将导致与第一个语句相同的计数,并相应地重新解释它,以免做无必要的工作。因此通常两个语句都会产生相同的执行计划并花费相同的时间。

但是从可读性的角度来看,你应该使用第一个语句。你想要计算记录数,所以就计算记录数,而不是表达式。只有在想要计算某些非空出现次数时,才使用COUNT(expression)。


8

我在一台8GB内存的Hyper-V虚拟机上对SQL Server 2012进行了快速测试,您可以自行查看结果。在运行这些测试时,我未运行除SQL Server Management Studio之外的其他窗口应用程序。

我的表模式:

CREATE TABLE [dbo].[employee](
    [Id] [bigint] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](50) NOT NULL,
 CONSTRAINT [PK_employee] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

Employee表中的记录总数:178090131(约1.78亿行)

第一个查询:

Set Statistics Time On
Go    
Select Count(*) From Employee
Go    
Set Statistics Time Off
Go

第一次查询的结果:

 SQL Server parse and compile time: 
 CPU time = 0 ms, elapsed time = 35 ms.

 (1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 10766 ms,  elapsed time = 70265 ms.
 SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

第二个查询:

    Set Statistics Time On
    Go    
    Select Count(1) From Employee
    Go    
    Set Statistics Time Off
    Go

第二个查询的结果:

 SQL Server parse and compile time: 
   CPU time = 14 ms, elapsed time = 14 ms.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 11031 ms,  elapsed time = 70182 ms.
 SQL Server parse and compile time: 
   CPU time = 0 ms, elapsed time = 0 ms.

您可以注意到,两次查询之间存在83(=70265-70182)毫秒的差异,这可以很容易地归因于运行查询时的确切系统条件。此外,我只进行了一次运行,如果我进行几次运行并进行一些平均化处理,这种差异将变得更加精确。如果对于如此庞大的数据集,差异小于100毫秒,那么我们可以轻松地得出结论,即SQL Server Engine没有表现出任何性能差异。请注意:在两次运行中,RAM接近100%的使用率。在开始两次运行之前,我重新启动了SQL Server服务。

7
SET STATISTICS TIME ON

select count(1) from MyTable (nolock) -- table containing 1 million records. 

SQL Server 执行时间:
CPU 时间 = 31 毫秒,经过时间 = 36 毫秒。

select count(*) from MyTable (nolock) -- table containing 1 million records. 

SQL Server 执行时间:
CPU 时间 = 46 毫秒,经过时间 = 37 毫秒。

我已经运行了这个程序数百次,每次都清除缓存...结果会因服务器负载的变化而有所不同,但几乎总是 count(*) 的 CPU 时间更高。


15
我无法复现这个问题。在我的 SQL 2008 实例中,即使对一个有450万行的表进行计数,count(*)count(1)的结果时间也相差不到几毫秒。 - Jeff Atwood
2
有时候在某些系统中,第一个被运行的语句总是执行地更快一些……你是否对它们运行的顺序进行了随机化处理? - JosephDoggie
@JosephDoggie 在进行此类测量/统计时,每次运行查询之前都应该重新启动SQL Server服务。当您刚刚启动SQL Server服务时,每次运行都变得完全独立,因此查询的顺序不应该有影响。另一方面,如果您不重新启动SQL Server服务并且引擎执行某种缓存执行计划,则稍后运行的查询应该比第一个查询运行得更快。 - RBT
执行时间需要在进行比较时查看确切的查询计划。如果它们不同(例如,哈希聚合与排序+流式聚合),则结果是不可比较的。因此,在没有更多数据的情况下,我建议谨慎得出结论。 - Conor Cunningham MSFT

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