select count(*) from table_name

返回表中的记录数

select count(DISTINCT column_name) from table_name

返回指定列的不同值的数目

select count(column_name(列名)) from table_name

返回指定列的数目

另外,效率方面的问题看到了一篇很好的文章。

count(*)与count(1)的对比count(col)与count(*)的对比count(col)与count(distinct col)比较索引与count的关系
堆表和聚集索引表上的count(*)非聚集索引上的count
结论参考资料

前言

记得很早以前就有人跟我说过,在使用count的时候要用count(1)而不要用count(*),因为使用count(*)的时候会对所有的列进行扫描,相比而言count(1)不用扫描所有列,所以count(1)要快一些。当时是对这一结论深信不疑,虽然不知道为什么。今天正好有时间研究研究看count(*)和count(1)到底有没有性能差异。

我的测试环境是SQL Server 2005 SP2开发版。

在进行测试之前先建立一些测试的数据,代码如下:

createtabletest(aint, bvarchar(100))
go
 
declare@nint
set@n= 1
while@n< 100000
begin
   if@n%3= 0
   insertintotestvalues(@n, null)
   if@n%3= 1
   insertintotestvalues(@n, str(@n))
   if@n%3= 2
   insertintotestvalues(@n, 'this is text')
   set@n= @n+1
end

这里先说明一下,为了测试的目的,test表里面是故意没有加索引的。

count(*)与count(1)的对比

现在我们开始验证count(*)和count(1)的区别,验证方法很简单,如果两个语句执行效率不一样的话它们的查询计划肯定会不一样的,我们先执行set showplan_text on打开SQL执行计划显示,然后我们执行相应的SQL语句。

先是count(*):

selectcount(*)fromtest
 
/*---------------------------------------------------------------------------
  ====== 下面是执行计划 ======
  |--Compute Scalar(DEFINE:([Expr1004]=CONVERT_IMPLICIT(int,[Expr1005],0)))
       |--Stream Aggregate(DEFINE:([Expr1005]=Count(*)))
            |--Table Scan(OBJECT:([AdventureWorks].[dbo].[test]))
 --------------------------------------------------------------------------*/

接着count(1):

selectcount(1)fromtest
 
/*---------------------------------------------------------------------------
 ====== 下面是执行计划 ======
  |--Compute Scalar(DEFINE:([Expr1004]=CONVERT_IMPLICIT(int,[Expr1005],0)))
       |--Stream Aggregate(DEFINE:([Expr1005]=Count(*)))
            |--Table Scan(OBJECT:([AdventureWorks].[dbo].[test]))
 --------------------------------------------------------------------------*/

对比下两个执行计划我们可以发现是完全一样的,这也就说明count(*)和count(1)的执行效率是完全一样的,根本不存在所谓的单列扫描和多列扫描的问题。

count(col)与count(*)的对比

同样,我们先看一下两个不同count方式的执行计划。

count(*)的执行计划看上面的例子。

count(b)的执行计划:

selectcount(b)fromtest
 
/*---------------------------------------------------------------------------
  ====== 下面是执行计划 ======
  |--Compute Scalar(DEFINE:([Expr1004]=CONVERT_IMPLICIT(int,[Expr1005],0)))
       |--Stream Aggregate(DEFINE:([Expr1005]=COUNT([AdventureWorks].[dbo].[test].[b])))
            |--Table Scan(OBJECT:([AdventureWorks].[dbo].[test]))
 --------------------------------------------------------------------------*/

现在能看到这两个执行计划唯一不同的地方就是COUNT的内容,对于count(*)是"|—Stream Aggregate(DEFINE:([Expr1005]=count(*)))",对于count(b)是"|—Stream Aggregate(DEFINE:([Expr1005]=COUNT([AdventureWorks].[dbo].[test].[b])))",那这两种count方式会不会有什么不一样呢?

让我们先看一下BOL里面对count(*)以及count(col)的说明:

COUNT(*) 返回组中的项数。包括 NULL 值和重复项。
COUNT(ALL expression) 对组中的每一行都计算 expression 并返回非空值的数量。

expression
除 text、image 或 ntext 以外任何类型的表达式。不允许使用聚合函数和子查询。

*
指定应该计算所有行以返回表中行的总数。COUNT(*) 不需要任何参数,而且不能与 DISTINCT 一起使用。
COUNT(*) 不需要 expression 参数,因为根据定义,该函数不使用有关任何特定列的信息。
COUNT(*) 返回指定表中行数而不删除副本。它对各行分别计数。包括包含空值的行。

也就是说count(*)只是返回表中行数,因此SQL Server在处理count(*)的时候只需要找到属于表的数据块块头,然后计算一下行数就行了,而不用去读取里面数据列的数据。而对于count(col)就不一样了,为了去除col列中包含的NULL行,SQL Server必须读取该col的每一行的值,然后确认下是否为NULL,然后在进行计数。因此count(*)应该是比count(col)快的,下面我们来验证一下。

我们通过在同样的条件下将select count(…) from test执行1000次来看两种count方式是否是一样的:

先看count(*)

declare@nint, @aint
set@n= 1
while@n<= 1000
begin
    select@a= count(*)fromtest
    set@n= @n+1
end
 
/*------------------------------
 执行结果:29s
 -----------------------------*/

接着看count(col)

declare@nint, @aint
set@n= 1
while@n<= 1000
begin
    select@a= count(b)fromtest
    set@n= @n+1
end
 
/*------------------------------
 执行结果:57s
 -----------------------------*/

从执行结果可以看出相差还是很大的,count(*)比count(col)快了一倍。

不过因为count(*)和count(col)使用的目的是不一样的,在必须要使用count(col)的时候还是要用的,只是在统计表全部行数的时候count(*)就是最佳的选择了。

另外:这里用到的跑1000次的方法也可以用在比较count(*)和count(1)上,在这里你将得到两个一样的执行时间。

count(col)与count(distinct col)比较

同样,我们先对比一下两个执行计划。

selectcount(b)fromtest
 
/*---------------------------------------------------------------------------
  ====== 下面是执行计划 ======
  |--Compute Scalar(DEFINE:([Expr1004]=CONVERT_IMPLICIT(int,[Expr1005],0)))
       |--Stream Aggregate(DEFINE:([Expr1005]=COUNT([AdventureWorks].[dbo].[test].[b])))
            |--Table Scan(OBJECT:([AdventureWorks].[dbo].[test]))
 --------------------------------------------------------------------------*/
 
 
selectcount(distinctb)fromtest
 
/*---------------------------------------------------------------------------
  ====== 下面是执行计划 ======
  |--Compute Scalar(DEFINE:([Expr1004]=CONVERT_IMPLICIT(int,[Expr1007],0)))
       |--Stream Aggregate(DEFINE:([Expr1007]=COUNT([AdventureWorks].[dbo].[test].[b])))
            |--Hash Match(Aggregate, HASH:([AdventureWorks].[dbo].[test].[b]),
            RESIDUAL:([AdventureWorks].[dbo].[test].[b] = [AdventureWorks].[dbo].[test].[b]))
                 |--Table Scan(OBJECT:([AdventureWorks].[dbo].[test]))
 --------------------------------------------------------------------------*/

从执行计划我们可以看到,因为表test没有索引,在执行count(distinct col)的时候是通过Hash Match的方式来查找相同值的行,这显然会耗费大量的CPU,同时我们也可以知道count(col)能比count(distinct col)快很多的。(如果test的列b有索引的话count(distinct col)的方式会不一样,走的是group by,但同样还是会比count(col)慢的,这个大家可以自己试一下)。

我们可以同样做一个执行1000次看花费的时间来做一个直观的对比。

declare@nint, @aint
set@n= 1
while@n<= 1000
begin
    select@a= count(b)fromtest
    set@n= @n+1
end
 
/*------------------------------
 执行结果:57s
 -----------------------------*/
 
 
declare@nint, @aint
set@n= 1
while@n<= 1000
begin
    select@a= count(distinctb)fromtest
    set@n= @n+1
end
 
/*------------------------------
 执行结果:2min 36s
 -----------------------------*/

索引与count的关系

我们上面讨论的都是表的索引结构不变的情况下count的变化,在表索引不变时对表做全表扫描所消耗的IO是不变的,不管是采取那种方式。现在在这里我们将看看不同类型的表索引对count会有什么样的变化,因为索引结构的改变对IO影响是最大的,在这里我们注重关注IO的变化情况。

先罗列一下我们要用到的SQL语句,包括查看IO,TIME、执行计划以及建立索引的。

-- 打开IO显示
setstatisticsioon
-- 打开执行时间显示
setstatisticstimeon
-- 打开执行计划显示
setshowplan_texton
 
-- 建立聚集索引pk_test
createclusteredindexpk_testontest(a)
-- 建立非聚集索引ix_a
createindexix_aontest(a)
-- 建立非聚集索引ix_b
createindexix_bontest(b)

堆表和聚集索引表上的count(*)

在这里我们先取得test没有建立索引之前执行count(*)的消耗,然后再在test上对a列建立一个聚集索引,然后再看看同样语句的执行计划和IO。

selectcount(*)fromtest
 
/*---------------------------------------------------------------------------
 ====== 对于堆表的执行计划 =====
  |--Compute Scalar(DEFINE:([Expr1004]=CONVERT_IMPLICIT(int,[Expr1005],0)))
       |--Stream Aggregate(DEFINE:([Expr1005]=Count(*)))
            |--Table Scan(OBJECT:([AdventureWorks].[dbo].[test]))
 ====== 对于堆表的执行时间和IO =====
SQL Server 分析和编译时间:
   CPU 时间 = 0 毫秒,占用时间 = 2 毫秒。
 
(1 行受影响)
表 'test'。扫描计数 1,逻辑读取 302 次,物理读取 0 次,预读 0 次,
lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
 
SQL Server 执行时间:
   CPU 时间 = 31 毫秒,占用时间 = 33 毫秒。
 
-----------------------------------------------------------------------------
 ====== 对于聚集索引表的执行计划 =====
  |--Compute Scalar(DEFINE:([Expr1004]=CONVERT_IMPLICIT(int,[Expr1005],0)))
       |--Stream Aggregate(DEFINE:([Expr1005]=Count(*)))
            |--Clustered Index Scan(OBJECT:([AdventureWorks].[dbo].[test].[pk_test]))
 ====== 对于聚集索引的执行时间和IO =====
SQL Server 分析和编译时间:
   CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
 
(1 行受影响)
表 'test'。扫描计数 1,逻辑读取 304 次,物理读取 0 次,预读 0 次,
lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
 
SQL Server 执行时间:
   CPU 时间 = 31 毫秒,占用时间 = 34 毫秒。
---------------------------------------------------------------------------*/

从实际测试我们可以看到,堆表和聚集索引表上的count是没有什么区别的,甚至于聚集索引表上的IO还要多2(这是因为多了两个聚集索引的数据块造成的)。如果你对聚集索引的结构很了解的话也是不难解释的:其实聚集索引并没有单独的保留所有索引列的信息,而只是将表中的行的物理顺序按照聚集索引列的顺序整理了一下,因此对聚集索引的扫描和对堆表的扫描是一样的,没有什么本质上的区别。

因此聚集索引对于count来说是没有帮助的。

非聚集索引上的count

现在我们执行前面给出的语句为test表增加一个非聚集索引ix_a然后看看执行计划和IO情况。

selectcount(*)fromtest
 
/*---------------------------------------------------------------------------
 ====== 对于非聚集索引表的执行计划 =====
  |--Compute Scalar(DEFINE:([Expr1004]=CONVERT_IMPLICIT(int,[Expr1005],0)))
       |--Stream Aggregate(DEFINE:([Expr1005]=Count(*)))
            |--Index Scan(OBJECT:([AdventureWorks].[dbo].[test].[ix_a]))
 ====== 对于非聚集索引表的执行时间和IO =====
SQL Server 分析和编译时间:
   CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
 
表 'test'。扫描计数 1,逻辑读取 126 次,物理读取 0 次,预读 0 次,
lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
 
SQL Server 执行时间:
   CPU 时间 = 31 毫秒,占用时间 = 32 毫秒。
---------------------------------------------------------------------------*/

从执行结果可以看到,逻辑读的次数明显的减少了,因为计算行数这个操作对于全表扫描或是非聚集索引的扫描结果是一样的,而相对来说非聚集索引的数据量是肯定会比表的数据量小很多的,同样的做一次全部扫描所花费的IO也就要少很多了。

同样的对于一个count(col)的操作来说,对col的索引做count同样是能达到count(col)的目的的,相比全表扫描一样可以节省很多的IO操作。

selectcount(a)fromtest
 
/*---------------------------------------------------------------------------
 ====== 对于非聚集索引表的执行计划 =====
  |--Compute Scalar(DEFINE:([Expr1004]=CONVERT_IMPLICIT(int,[Expr1005],0)))
       |--Stream Aggregate(DEFINE:([Expr1005]=COUNT([AdventureWorks].[dbo].[test].[a])))
            |--Index Scan(OBJECT:([AdventureWorks].[dbo].[test].[ix_a]))
 ====== 对于非聚集索引表的执行时间和IO =====
SQL Server 分析和编译时间:
   CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
 
表 'test'。扫描计数 1,逻辑读取 126 次,物理读取 0 次,预读 0 次,
lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
 
SQL Server 执行时间:
   CPU 时间 = 46 毫秒,占用时间 = 49 毫秒。
---------------------------------------------------------------------------*/

结论

这里把上面实验的结果总结一下:

  • count(*)和count(1)执行的效率是完全一样的。
  • count(*)的执行效率比count(col)高,因此可以用count(*)的时候就不要去用count(col)。
  • count(col)的执行效率比count(distinct col)高,不过这个结论的意义不大,这两种方法也是看需要去用。
  • 如果是对特定的列做count的话建立这个列的非聚集索引能对count有很大的帮助。
  • 如果经常count(*)的话则可以找一个最小的col建立非聚集索引以避免全表扫描而影响整体性能。

当然,在建立优化count的索引之前一定要考虑新建立的索引会不会对别的查询有影响,影响有多大,要充分考虑之后再决定是否要这个索引,这是很重要的一点,不要捡了芝麻丢了西瓜。

转载于:https://www.cnblogs.com/axyz/archive/2011/02/23/1962539.html

select count相关推荐

  1. 数据库中的select 1,select count(1),order by 1

    select 1 from table;表示增加临时列,查到的临时行的值都是1,table中有多少行,就有多少的1 select count(1) from table,表示查出表的行数,把1替换成任 ...

  2. 我说 SELECT COUNT(*) 会造成全表扫描,面试官让我回去等通知

    来自:码海 前言 上篇 SQL 进阶技巧(下) 中提到使用以下 sql 会导致慢查询 SELECT COUNT(*) FROM SomeTable SELECT COUNT(1) FROM SomeT ...

  3. 你知道select count(*)底层究竟干了啥么?

    作者:贾春生 https://blog.didiyun.com/index.php/2019/01/08/mysql-count/ "SELECT COUNT( * ) FROM T&quo ...

  4. select count(*)加其他字段_count(1)、count(*) 与 count(列名) 的执行区别

    (给ImportNew加星标,提高Java技能) 作者:BigoSprite blog.csdn.net/iFuMI/article/details/77920767 执行效果: 1.  count( ...

  5. select count(*) from temp 与select count(1) from temp有什么区别

    select count(*) from temp 与select count(1) from temp有什么区别 ----count(*)是整个表中有多少条记录,扫描的是整个表 ---- ----c ...

  6. [MySQL FAQ]系列 -- 为何innodb表select count(*)很慢

    innodb表不像myisam,有个内置的计数器. 1. 可以用 select count(*) from table_name where primary_key >=0 2. 或 selec ...

  7. [Oracle] “表中有数据,但select count(*)的结果为0”问题的解决办法

    [Oracle] "表中有数据,但select count(*)的结果为0"问题的解决办法 参考文章: (1)[Oracle] "表中有数据,但select count( ...

  8. MySQL:SELECT COUNT 小结

    作者 | 翁智华 来源 | https://www.jianshu.com/p/4913fdd1e277 背景 今天团队在做线下代码评审的时候,发现同学们在代码中出现了 select count(1) ...

  9. Select count(*) 的优化

    首先说明: select count(*) 和 select count(1)的效率相差无几. 这里开始引用自"德哥@Digoal"的博客,原文链接:http://blog.163 ...

最新文章

  1. oracle创建数据库后干什么,手动创建Oracle数据库之前因后果
  2. SubVersion和Subclipse的简单使用方法
  3. 《Swift开发实战》——第2章,第2.4节函数和闭包
  4. Hibernate 关联映射 之 多对多 关联(二) 之拆分
  5. STL常用的遍历算法
  6. memcached和redis的区别
  7. 【第二届】Erlang Fans交流会(补充事宜)
  8. 使用python 创建快捷方式
  9. java 广告插件_徒手创建一个chrome扩展-屏蔽广告插件
  10. python必背代码-让你的python代码优雅地道的小技巧
  11. python 写入excel 效率_python各种excel写入方式的速度对比
  12. 编译thrift和使用 - 翱翔云颠的博客 - 我的搜狐
  13. flex + tomcat + myEclipse环境配置与使用(四)
  14. html5上传steam,Steam.html · savfile/shencore.github.io - Gitee.com
  15. 澳洲的10种房屋类型
  16. 数仓(六)从0到1简单搭建数仓ODS层(埋点日志 + 业务数据)
  17. 自学网络安全,一般人我劝你放弃吧!
  18. 【盘点】值得推荐的优质文章!
  19. ppt怎么转换成word文档呢?
  20. VC工程中的.rc文件和.rc2文件的区别

热门文章

  1. 洛谷P4199 万径人踪灭(manacher+FFT)
  2. Http Hijacker
  3. C#23种开发模式,陆续完善中
  4. tabBar的图标不被系统渲染
  5. 《从零开始学Swift》学习笔记(Day 26)——可选链
  6. C语言中关于字符串常量的进一步分析 转
  7. sniffer使用查网络故障
  8. Exchange邮件服务器实现外部邮件的收发
  9. Java Web项目中解决中文乱码方法总结
  10. 部署与管理ZooKeeper