Count(*)vs Count(1)-SQL服务器

只是想知道你们中是否有人使用Count(1)而不是Count(*),如果性能有明显的差异,或者这只是过去几天带来的传统习惯?

具体数据库为SQL Server 2005

488383 次浏览

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

与任何事情一样,唯一真正的方法是衡量你的具体情况。

也就是说,我一直使用COUNT(*)

在SQLServer中,这些语句产生相同的计划。

与流行的观点相反,在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(*)的参数)

没有区别。

理由:

在线图书表示“COUNT ( { [ [ ALL | DISTINCT ] expression ] | * } )

“1”是一个非空表达式:所以它与COUNT(*)相同。优化器识别它是什么:平凡。

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

示例:

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

同样的IO,同样的计划,同样的工作

2011年8月

类似问题DBA.SE

2011年12月编辑

COUNT(*)ANSI-92中特别提到(查找“Scalar expressions 125”)

案例:

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

也就是说,ANSI标准认为它是显而易见的。COUNT(1)已经被RDBMS供应商因为优化了。否则,它将根据ANSI进行评估

b)否则,令TX为单列表,即将应用于T的每一行的结果和消除空值。如果一个或多个空值是删除,则引发完成条件:warning-

显然,COUNT(*)COUNT(1)总是返回相同的结果。因此,如果一个比另一个慢,那实际上是由于优化器bug。由于这两种形式在查询中使用非常频繁,DBMS允许这样的bug保持不固定是没有意义的。因此,您会发现这两种形式的性能在所有主要SQLDBMS中(可能)相同。

在结果和性能的情况下,COUNT(*)COUNT(1)是相同的。

SET STATISTICS TIME ON
select count(1) from MyTable (nolock) -- table containing 1 million records.

SQL服务器执行时间:
CPU时间=31毫秒,运行时间=36毫秒。

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

SQL服务器执行时间:
CPU时间=46 ms,运行时间=37 ms。

我已经运行了数百次,每次都清除缓存…结果随着服务器负载的变化而不时变化,但几乎总是count(*)具有更高的cpu时间。

在SQL-92标准中,COUNT(*)具体表示“表表达式的基数”(可以是基表、'VIEW、派生表、CTE等)。

我想这个想法是COUNT(*)很容易解析。使用任何其他表达式都需要解析器确保它不引用任何列(COUNT('a')其中a是文字,COUNT(a)其中a是列可以产生不同的结果)。

同样,熟悉SQL标准的人类编码人员可以轻松挑选出COUNT(*),这是使用多个供应商的SQL产品时的有用技能。

此外,在特殊情况SELECT COUNT(*) FROM MyPersistedTable;中,考虑的是DBMS可能保存表基数的统计信息。

因此,因为COUNT(1)COUNT(*)在语义上是等价的,所以我使用COUNT(*)

随着这个问题的反复出现,这里还有一个答案。我希望在这里为想知道“最佳实践”的初学者添加一些东西。

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

SELECT COUNT(1) FROM something检索每条记录的1,然后计数不为空的1,这本质上是计数记录,只是更复杂。

话虽如此:好的dbms会注意到第二条语句将产生与第一条语句相同的计数,并相应地重新解释它,以免做不必要的工作。所以通常两个语句将导致相同的执行计划并花费相同的时间。

然而,从易读性的角度来看,你应该使用第一条语句。你想计数记录,所以计数记录,而不是表达式。只有当你想计数某物的非空发生时,才使用COUNT(表达式)。

我在SQLServer 2012上运行了一个8 GB RAM hyper-v框的快速测试。您可以自己查看结果。在运行这些测试时,除了SQLServer 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 OnGoSelect Count(*) From EmployeeGoSet Statistics Time OffGo

第一次查询结果:

 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 OnGoSelect Count(1) From EmployeeGoSet Statistics Time OffGo

第二次查询结果:

 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服务器引擎表现出的任何性能差异。

说明:两次运行的RAM使用率都接近100%。在开始两次运行之前,我重新启动了SQL服务器服务。

COUNT(1)与COUNT(*)没有实质性的区别,如果有的话。至于计算可空列的问题,这可以简单地演示COUNT(*)和COUNT(<一些列>)之间的区别--

USE tempdb;GO
IF OBJECT_ID( N'dbo.Blitzen', N'U') IS NOT NULL DROP TABLE dbo.Blitzen;GO
CREATE TABLE dbo.Blitzen (ID INT NULL, Somelala CHAR(1) NULL);
INSERT dbo.Blitzen SELECT 1, 'A';INSERT dbo.Blitzen SELECT NULL, NULL;INSERT dbo.Blitzen SELECT NULL, 'A';INSERT dbo.Blitzen SELECT 1, NULL;
SELECT COUNT(*), COUNT(1), COUNT(ID), COUNT(Somelala) FROM dbo.Blitzen;GO
DROP TABLE dbo.Blitzen;GO

有一个文章显示Oracle上的COUNT(1)只是COUNT(*)的别名,其中有一个证明

我将引用一些部分:

数据库软件中有一部分称为“The优化器”,在官方留档中定义为”内置数据库软件,确定最有效的方法来执行SQL语句”。

优化器的一个组件称为“变压器”,其作用是确定重写原始SQL语句转换为语义等价的SQL语句这样会更有效率

当您编写查询时,您想看看优化器会做什么吗使用COUNT(1)?

对于具有ALTER SESSION权限的用户,您可以放置tracefile_identifier,启用优化器跟踪并运行COUNT(1)选择,例如:SELECT /* test-1 */ COUNT(1) FROM employees;

之后,您需要本地化跟踪文件,可以使用SELECT VALUE FROM V$DIAG_INFO WHERE NAME = 'Diag Trace';完成。稍后在文件中,您会发现:

SELECT COUNT(*) “COUNT(1)” FROM “COURSE”.”EMPLOYEES” “EMPLOYEES”

如您所见,它只是COUNT(*)的别名。

另一个重要的评论:在Oracle 7.3之前,COUNT(*)在Oracle上确实更快二十年前

Count(1)自7.3以来已被重写为count(*),因为Oracle喜欢到自动调整神话语句。在早期的Oracle7中,Oracle必须对每一行求值(1),作为函数,在DETERMINISTIC和

不确定的存在。

所以二十年前,计数(*)更快

对于另一个数据库,如Sql Server,应该为每个数据库单独研究。

我知道这个问题是针对SQL服务器的,但是SO上关于同一主题的其他问题(没有提到特定的数据库)被关闭并标记为与此答案重复。

我在SQL服务器团队工作,我希望能在这个线程中澄清几点(我以前没有见过,所以我很抱歉工程团队以前没有这样做)。

首先,select count(1) from tableselect count(*) from table之间没有语义差异。它们在所有情况下返回相同的结果(如果没有,这是一个bug)。如其他答案所述,select count(column) from table在语义上不同,并不总是返回与count(*)相同的结果。

其次,关于性能,有两个方面在SQLServer(和SQLAzure)中很重要:编译时工作和执行时工作。编译时工作在当前实现中是微不足道的少量额外工作。由于一些内部操作在绑定和优化中的工作方式,在某些情况下会将*扩展到所有列,然后将输出减少到1列。我怀疑它会出现在任何可测量的测试中,并且它很可能会消失在所有其他暗中发生的事情(例如自动统计、xEvent会话、查询存储开销、触发器等)的噪音中。这可能是几千条额外的CPU指令。因此,在编译期间,count(1)做的工作少了一点(通常发生一次,并且计划缓存在多个后续执行中)。对于执行时间,假设计划相同,应该没有可测量的差异。(前面的一个例子显示了差异——如果计划相同,很可能是由于机器上的其他因素造成的)。

至于计划可能会有什么不同。这些极不可能发生,但在当前优化器的架构中是可能的。SQL服务器的优化器作为一个搜索程序(想想:计算机程序下棋,为查询的不同部分搜索各种替代方案,并在合理的时间内计算替代方案的成本,以找到最便宜的计划)。这种搜索在如何保持查询编译在合理的时间内完成方面有一些限制。对于最琐碎的查询,搜索有几个阶段,它们根据优化器认为查询可能执行的成本来处理部分查询。有3个主要的搜索阶段,每个阶段都可以运行更激进(昂贵)的启发式方法,试图找到比任何先前的解决方案更便宜的计划。最终,每个阶段结束时都有一个决策过程,试图确定它是应该返回到目前为止找到的计划,还是应该继续搜索。这个过程使用到目前为止所花费的总时间与到目前为止找到的最佳计划的估计成本。因此,在具有不同CPU速度的不同机器上,由于计划在早期阶段超时而不是继续进入下一个搜索阶段,有可能(尽管很少)得到不同的计划。还有一些类似的场景,与最后阶段超时有关,并可能在非常非常昂贵的查询上运行内存溢出,这些查询消耗了机器上的所有内存(在64位上通常不是问题,但在32位服务器上是一个更大的问题)。最终,如果你得到不同的计划,运行时的性能会有所不同。我认为编译时间的差异不太可能导致这些情况发生。

Net-net:请使用你想要的任何一个,因为这在任何实际形式中都无关紧要。(老实说,除了这个主题之外,影响SQL性能的因素要大得多)。

我希望这能有所帮助。我确实写了一本关于优化器如何工作的书章节,但我不知道是否适合在这里发布(因为我仍然相信从中获得很少的版税)。所以,与其发布,我将发布一个链接,链接到我在英国的SQLBits上发表的关于优化器如何在高层工作的演讲,这样如果你想了解更多细节,你就可以更详细地看到搜索的不同主要阶段。这是视频链接:https://sqlbits.com/Sessions/Event6/inside_the_sql_server_query_optimizer

在所有的RDBMS中,两种计数方式在它们产生的结果方面是等价的。关于性能,我没有观察到SQLServer中的任何性能差异,但可能值得指出的是,一些RDBMS,例如PostgreSQL 11,对#0的最佳实现较少,因为它们检查参数表达式的可空性,如本文所示

我发现运行时1M行有10%的性能差异:

-- FasterSELECT COUNT(*) FROM t;
-- 10% slowerSELECT COUNT(1) FROM t;