SQLSERVER 里SELECT COUNT(1) 和SELECT COUNT(*)哪个性能好?

今天遇到某人在我以前写的一篇文章里问到

如果统计信息没来得及更新的话,那岂不是统计出来的数据时错误的了

这篇文章的地址:SQLSERVER是怎麽通过索引和统计信息来找到目标数据的(第三篇)

之前我以为SELECT COUNT(*)是根据统计信息来的,但是后来想了一下,这个肯定不是

那么SQLSERVER怎麽统计SELECT COUNT(*)的呢??

其实SQLSERVER也是使用扫描的方法

大家也可以先看一下:SQLSERVER中的ALLOCATION SCAN和RANGE SCAN

但是这里不讨论是ALLOCATION SCAN还是RANGE SCAN,大家知道SQLSERVER使用的是扫描的方式就可以了


聚集索引表

SQL脚本如下:

  1. USE [pratice]
  2. GO
  3.  
  4. --建立聚集索引表
  5. CREATE TABLE ct1(c1 INT, c2 VARCHAR (2000));
  6. GO
  7. --建立聚集索引
  8. CREATE CLUSTERED INDEX t1c1 ON ct1(c1);
  9. GO
  10.  
  11. --插入测试数据
  12. DECLARE @a INT;
  13. SELECT @a = 1;
  14. WHILE (@a <= 12)
  15. BEGIN
  16. INSERT INTO ct1 VALUES (@a, replicate('a', 2000))
  17. SELECT @a = @a + 1
  18. END
  19. GO
  20.  
  21. --查询数据
  22. SELECT * FROM ct1

看一下执行计划

(图片一)

  1. SET STATISTICS PROFILE ON
  2. GO
  3. SELECT COUNT(*) FROM [dbo].[ct1]

(图片二)

这里需要了解流聚合运算符

MSDN对于流聚合运算符的解释

(图片三)

宋沄剑的文章里也有对流聚合运算符的解释

SQL Server中的执行引擎入门

重点是理解:Stream Aggregate 运算符按一列或多列对行分组,然后计算由查询返回的一个或多个聚合表达式

Stream Aggregate 运算符按一列对行分组,然后计算由查询返回的一个聚合表达式

我们用下面两个图会清楚一些

(图片四)

(图片五)

SQLSERVER对表中的行分组进行扫描,但是SQLSERVER以多少行为一组来进行扫描呢??这个不得而知了

为什麽要使用流聚合?

大家一定会自然而然地想到分组统计提高性能,特别是表中数据量非常大的时候,分组统计特别有用

计算标量运算符只是把聚合的结果隐式转换为int类型

大家知道ct1表只有两列,但是SELECT COUNT(3) FROM [dbo].[ct1]也能够返回表中的行数

  1. SELECT COUNT(1) FROM [dbo].[ct1]
  1. SELECT COUNT(3) FROM [dbo].[ct1]

(图片六)

就算用列名都是一样的执行计划

  1. SELECT COUNT(c1) FROM [dbo].[ct1]
  2. SELECT COUNT(c2) FROM [dbo].[ct1]

(图片七)

SQLSERVER究竟以哪一列来进行表的行数统计的呢??????

答案就在

Stream Aggregate 运算符要求输入的数据要按某列进行排序,如果由于前面的 Sort 运算符或已排序的索引查找或扫描导致数据尚未排序,

则优化器将在此运算符前面使用一个 Sort 运算符,使表的某列是有序排序的。

  1. SELECT COUNT(*)
  2. SELECT count3
  3. SELECT countc2

(图片八)

上面三个SQL语句都是按照聚集索引的第一个字段(ct1表中的c1列)来进行统计的

因为聚集索引的第一个字段是根据建立聚集索引的时候的排序顺序预先排好序

Stream Aggregate 运算符要求输入的数据要按某列进行排序

所以无论是指定字段名、*还是数字,都是根据聚集索引的第一个字段来统计


堆表

SQL脚本如下:

  1. CREATE TABLE t1(c1 INT, c2 VARCHAR (8000));
  2. GO
  3.  
  4. --插入测试数据
  5.  
  6. DECLARE @a INT;
  7. SELECT @a = 1;
  8. WHILE (@a <= 12)
  9. BEGIN
  10. INSERT INTO t1 VALUES (@a, replicate('a', 5000))
  11. SELECT @a = @a + 1
  12. END
  13. GO
  14.  
  15. --查询数据
  16. SELECT * FROM t1

(图片九)

(图片十)

堆表这里使用的是ALLOCATION SCAN

因为分配页面的时候是根据c1列的值从1~12进行分配的

(图片十一)

109页面存放的c1值是1

120页面存放的c1值是2

174页面存放的c1值是3

193页面存放的c1值是4

8316页面存放的c1值是5

8340页面存放的c1值是6

8351页面存放的c1值是7

8353页面存放的c1值是8

(图片十二)

这里执行计划在流聚合之前并没有进行排序的原因:因为建表进行页面分配的时候已经按照C1列的值进行有序的页面分配

所以当ALLOCATION SCAN的时候,C1列已经是有序的了

(图片十三)

不明白的童鞋可以再看一下:SQLSERVER中的ALLOCATION SCAN和RANGE SCAN

为什麽SQLSERVER选择统计C1列的值,因为C1列的值是可以排序的,C2列不能排序,统计不了

那么如果一个表中没有可以用来排序的列呢????

先drop掉t1表,再建立t1表,脚本如下:

  1. CREATE TABLE t1(c1 VARCHAR (2), c2 VARCHAR (8000));
  2. GO
  3.  
  4. --插入测试数据
  5. DECLARE @a INT;
  6. SELECT @a = 1;
  7. WHILE (@a <= 12)
  8. BEGIN
  9. INSERT INTO t1 VALUES ('a', replicate('a', 5000))
  10. SELECT @a = @a + 1
  11. END
  12. GO
  13.  
  14. --查询数据
  15. SELECT * FROM t1

结果是

(图片十四)

我觉得SQLSERVER应该会在表中加上一列,类似用来区分聚集索引页面重复值的UNIQUIFIER(KEY)

当查询完毕之后就删除掉这一列

(图片十五)


非聚集索引表

SQL脚本如下:

  1. CREATE TABLE nct1(c1 INT, c2 VARCHAR (8000));
  2. GO
  3. --建立非聚集索引
  4. CREATE INDEX nt1c1 ON nct1(c1);
  5. GO
  6.  
  7. --插入数据
  8. DECLARE @a INT;
  9. SELECT @a = 1;
  10. WHILE (@a <= 10)
  11. BEGIN
  12. INSERT INTO nct1 VALUES (@a, replicate('a', 5000))
  13. SELECT @a = @a + 1
  14. END
  15. GO
  16.  
  17. --查询数据
  18. SELECT * FROM [dbo].[nct1]

(图片十六)

大家一定要记住:非聚集索引是建立在c1列上的!!!

下面两个SQL语句都是一样的,都是根据c1列的值进行统计,而SQLSERVER只扫描非聚集索引页面,而不扫描数据页面

  1. SELECT COUNT(*) FROM [dbo].[nct1]
  2.  
  3. SELECT COUNT(3) FROM [dbo].[nct1]

SELECT  COUNT(*) FROM [dbo].[nct1]是不需要到数据页面去读取c2列的数据的,只需要扫描非聚集索引页面(c1列)就可以了

SELECT  COUNT(3) FROM [dbo].[nct1]跟SELECT  COUNT(*) FROM [dbo].[nct1]也是一样

不知道大家还记得书签查找不,如果SQLSERVER扫描了非聚集索引页面之后还需要到数据页面去读取其他字段的数据的话,就需要RID查找运算符

(图片十七)

SQLSERVER聚集索引与非聚集索引的再次研究(下)

SELECT  COUNT(*) FROM [dbo].[nct1]和SELECT  COUNT(3) FROM [dbo].[nct1]的扫描方式跟前面说的聚集索引表是差不多的

这里就不一一叙述了~

而SELECT  COUNT(c2) FROM [dbo].[nct1]为什麽会用表扫描呢?

  1. SELECT COUNT(c2) FROM [dbo].[nct1]

c2列不在非聚集索引页面里,所以需要表扫描

(图片十八)

SELECT  COUNT(c2) FROM [dbo].[nct1]跟前面说的堆表是差不多的,这里就不一一叙述了


总结

做了这麽多实验

可以总结出:select count(*)、count(数字)、count(字段名)是没有性能差别的!!

我说的没有差别是在相同的条件下,就像非聚集索引表,如果使用

SELECT  COUNT(c2) FROM [dbo].[nct1]

SELECT  COUNT(*) FROM [dbo].[nct1]、SELECT  COUNT(3) FROM [dbo].[nct1]相比肯定有差别

因为SELECT  COUNT(c2) FROM [dbo].[nct1]走的是表扫描

如果SELECT  COUNT(c1) FROM [dbo].[nct1]

SELECT  COUNT(*) FROM [dbo].[nct1]、SELECT  COUNT(3) FROM [dbo].[nct1]相比是没有差别的

(图片十九)

大家走的都是非聚集索引扫描

无论是聚集索引表、堆表、非聚集索引表都是扫描表中的记录来统计出表中的行数的

希望大家看完这篇文章之后,不再一知半解了,这是我的希望o(∩_∩)o

如有不对的地方,欢迎大家拍砖o(∩_∩)o

-----------------------------------------------------------------------

补上IO和时间的比较 2013-10-19

---------------------------------

聚集索引表

  1. SET STATISTICS IO ON
  2. SET STATISTICS TIME ON
  3. GO
  4. SELECT COUNT(*) FROM [dbo].[ct1]
  1. SQL Server 分析和编译时间:
  2. CPU 时间 = 0 毫秒,占用时间 = 2 毫秒。
  3.  
  4. (1 行受影响)
  5. 'ct1'。扫描计数 1,逻辑读取 5 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
  6.  
  7. SQL Server 执行时间:
  8. CPU 时间 = 15 毫秒,占用时间 = 2 毫秒。
  1. SET STATISTICS IO ON
  2. SET STATISTICS TIME ON
  3. GO
  4. SELECT COUNT(1) FROM [dbo].[ct1]
  1. SQL Server 分析和编译时间:
  2. CPU 时间 = 0 毫秒,占用时间 = 2 毫秒。
  3.  
  4. (1 行受影响)
  5. 'ct1'。扫描计数 1,逻辑读取 5 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
  6.  
  7. SQL Server 执行时间:
  8. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  1. SET STATISTICS IO ON
  2. SET STATISTICS TIME ON
  3. GO
  4. SELECT COUNT(c1) FROM [dbo].[ct1]
  1. SQL Server 分析和编译时间:
  2. CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
  3.  
  4. (1 行受影响)
  5. 'ct1'。扫描计数 1,逻辑读取 5 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
  6.  
  7. SQL Server 执行时间:
  8. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。

---------------------------------------------------

堆表

  1. SET STATISTICS IO ON
  2. SET STATISTICS TIME ON
  3. GO
  4. SELECT COUNT(*) FROM [dbo].[t1]
  1. SQL Server 分析和编译时间:
  2. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  3.  
  4. SQL Server 执行时间:
  5. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  6.  
  7. SQL Server 执行时间:
  8. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  9. SQL Server 分析和编译时间:
  10. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  11.  
  12. (1 行受影响)
  13. 't1'。扫描计数 1,逻辑读取 12 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
  14.  
  15. SQL Server 执行时间:
  16. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  1. SET STATISTICS IO ON
  2. SET STATISTICS TIME ON
  3. GO
  4. SELECT COUNT(1) FROM [dbo].[t1]
  1. SQL Server 分析和编译时间:
  2. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  3.  
  4. SQL Server 执行时间:
  5. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  6.  
  7. SQL Server 执行时间:
  8. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  9. SQL Server 分析和编译时间:
  10. CPU 时间 = 0 毫秒,占用时间 = 79 毫秒。
  11.  
  12. (1 行受影响)
  13. 't1'。扫描计数 1,逻辑读取 12 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
  14.  
  15. SQL Server 执行时间:
  16. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  1. SET STATISTICS IO ON
  2. SET STATISTICS TIME ON
  3. GO
  4. SELECT COUNT(c1) FROM [dbo].[t1]
  1. SQL Server 分析和编译时间:
  2. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  3.  
  4. SQL Server 执行时间:
  5. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  6.  
  7. SQL Server 执行时间:
  8. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  9. SQL Server 分析和编译时间:
  10. CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
  11.  
  12. (1 行受影响)
  13. 't1'。扫描计数 1,逻辑读取 12 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
  14.  
  15. SQL Server 执行时间:
  16. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。

-----------------------------------------------------------------------------------------

非聚集索引表

  1. SET STATISTICS IO ON
  2. SET STATISTICS TIME ON
  3. GO
  4. SELECT COUNT(*) FROM [dbo].[nct1]
  1. SQL Server 分析和编译时间:
  2. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  3.  
  4. SQL Server 执行时间:
  5. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  6.  
  7. SQL Server 执行时间:
  8. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  9. SQL Server 分析和编译时间:
  10. CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。
  11.  
  12. (1 行受影响)
  13. 'nct1'。扫描计数 1,逻辑读取 2 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
  14.  
  15. SQL Server 执行时间:
  16. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  1. SET STATISTICS IO ON
  2. SET STATISTICS TIME ON
  3. GO
  4. SELECT COUNT(1) FROM [dbo].[nct1]
  1. SQL Server 分析和编译时间:
  2. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  3.  
  4. SQL Server 执行时间:
  5. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  6.  
  7. SQL Server 执行时间:
  8. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  9. SQL Server 分析和编译时间:
  10. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  11.  
  12. (1 行受影响)
  13. 'nct1'。扫描计数 1,逻辑读取 2 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
  14.  
  15. SQL Server 执行时间:
  16. CPU 时间 = 0 毫秒,占用时间 = 49 毫秒。
  1. SET STATISTICS IO ON
  2. SET STATISTICS TIME ON
  3. GO
  4. SELECT COUNT(c1) FROM [dbo].[nct1]
  1. SQL Server 分析和编译时间:
  2. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  3.  
  4. SQL Server 执行时间:
  5. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  6.  
  7. SQL Server 执行时间:
  8. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  9. SQL Server 分析和编译时间:
  10. CPU 时间 = 0 毫秒,占用时间 = 0 毫秒。
  11.  
  12. (1 行受影响)
  13. 'nct1'。扫描计数 1,逻辑读取 2 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
  14.  
  15. SQL Server 执行时间:
  16. CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。

2014-6-21补充:

  1. USE [sss]
  2. --建表
  3. CREATE TABLE counttb ( id INT NULL )
  4.  
  5. --插入数据
  6. INSERT INTO [dbo].[counttb]
  7. ( [id] )
  8. SELECT 1
  9. UNION ALL
  10. SELECT NULL
  11.  
  12. --统计行数
  13. SELECT COUNT(1) ,
  14. COUNT(*) ,
  15. COUNT(id)
  16. FROM [dbo].[counttb]
  17.  
  18. --查询索引的统计值
  19. SELECT a.[rowcnt] ,
  20. b.[name]
  21. FROM sys.[sysindexes] AS a
  22. INNER JOIN sys.[objects] AS b ON a.[id] = b.[object_id]
  23. WHERE b.[name] = 'counttb'
  24.  
  25. --创建非聚集索引
  26. CREATE INDEX ix_counttb_id ON [dbo].[counttb] (id)
  27.  
  28. --统计行数
  29. SELECT COUNT(1) ,
  30. COUNT(*) ,
  31. COUNT(id)
  32. FROM [dbo].[counttb]

因为在创建非聚集索引前和创建非聚集索引后的行数值都是一样的,可以看出COUNT(*) COUNT(1) 和COUNT(ID)

的统计方式不一样,所以没有可比性

一般我们在统计行数的时候都会把NULL值统计在内的,所以这样的话,最好就是使用COUNT(*) 和COUNT(1) ,这样的速度最快!!

SQLSERVER 里SELECT COUNT(1) 和SELECT COUNT(*)哪个性能好?的更多相关文章

  1. JS中Float类型加减乘除 修复 JQ 操作 radio、checkbox 、select LINQ to SQL:Where、Select/Distinct LINQ to SQL Count/Sum/Min/Max/Avg Join

    JS中Float类型加减乘除 修复   MXS&Vincene  ─╄OvЁ  &0000027─╄OvЁ  MXS&Vincene MXS&Vincene  ─╄Ov ...

  2. PHP基础语法: echo,var_dump, 常用函数:随机数:拆分字符串:explode()、rand()、日期时间:time()、字符串转化为时间戳:strtotime()可变参数的函数:PHP里数组长度表示方法:count($attr[指数组]);字符串长度:strlen($a)

    PHP语言原理:先把代码显示在源代码中,再通过浏览器解析在网页上 a. 1.substr;  //用于输出字符串中,需要的某一部分 <?PHP $a="learn php"; ...

  3. sqlserver不能直接create table as select

    sqlserver不能直接create table as select 在sqlserver 下想复制一张表的,想到oracle下直接create table xxx as select * from ...

  4. COUNT(*),count(1),COUNT(ALL expression),COUNT(DISTINCT expression)

    创建一个测试表 IF OBJECT_ID( 'dbo.T1' , 'U' )IS NOT NULL BEGIN DROP TABLE dbo.T1; END; GO )); GO INSERT INT ...

  5. SQLSERVER 里经常看到的CACHE STORES是神马东东?

    SQLSERVER 里经常看到的CACHE STORES是神马东东? 当我们在SSMS里执行下面的SQL语句清空SQLSERVER的缓存的时候,我们会在SQL ERRORLOG里看到一些信息 DBCC ...

  6. 用SQLSERVER里的bcp命令或者bulkinsert命令也可以把dat文件导入数据表

    用SQLSERVER里的bcp命令或者bulkinsert命令也可以把dat文件导入数据表 下面的内容的实验环境我是在SQLSERVER2005上面做的 之前在园子里看到两篇文章<C# 读取纯真 ...

  7. MySQL select into 和 SQL select into

    现在有张表为student,我想将这个表里面的数据复制到一个为dust的新表中去,虽然可以用以下语句进行复制,总觉得不爽,希望各位帮助下我,谢谢.  answer 01: create table d ...

  8. hql中不能写count(1)能够写count(a.id)

    hql中不能写count(1)能够写count(a.id)里面写详细的属性 String hql="select new com.haiyisoft.vo.entity.cc.repo.Bu ...

  9. mybatis中union可以用if判断连接,但是<select>中第一个select语句不能被if判断,因此可以从dual表中查询null来凑齐。union如果使用order by排序,那么只能放在最后一个查询语句的位置,并且不能带表名。

    <!-- 一址多证纳税人分析表 --> <select id="yzdznsrlistPage" parameterType="page" r ...

随机推荐

  1. android百度地图相关

    1.如果有报错Multiple dex files define Lcom/baidu/android/bbalbs/common/a/a一般是有重复jar包.    2.百度地图开发调试的应用程序正 ...

  2. 论人品 | | noip1015模拟考

    第一题:火车进站... 由于有了老师给的助攻,第一题的时间为半小时,主要在读题了.... jzoj1146 第二题:car 难在正方形的计算? 第二题时间:1.5hour 第三题:sort排序?

  3. 一个未解决的samba问题

    话说,现在的打复印扫描一体机的扫描功能十分丰富,扫描后的文件可以通过邮件发送,可以发到windows的共享.一直用着windows共享的方式,但是windows系统占用的内存还是略大,想把这个共享放到 ...

  4. touchstart,touchmove判断手机中滑屏方向

    滑动屏幕    touchstart:接触屏幕时触发,touchmove:活动过程触发,touchend:离开屏幕时触发 首先获取手接触屏幕时的坐标X,Y //获取接触屏幕时的X和Y$('body') ...

  5. Android NDK, No rule to make target

    这种问题一般是android.mk里面没有找到对应的源文件 http://stackoverflow.com/questions/11570167/android-ndk-no-rule-to-mak ...

  6. python configparser模块

    来看一个好多软件的常见文档格式如下: [DEFAULT] ServerAliveInterval = 45 Compression = yes CompressionLevel = 9 Forward ...

  7. Intelij IDEA 2016.3安装mybatis插件并激活教程

    转载自:http://blog.csdn.net/solo_talk/article/details/53540449 现在Mybatis框架越来越受欢迎,Intelij IDEA这个编辑器逐渐成为很 ...

  8. LeetCode OJ-- Single Number II **@

    有一列数,其中有1个数出现了1次,其它数都出现了3次,求这个数. class Solution { public: int singleNumber(int A[], int n) { ) ; ; ; ...

  9. python 装饰器修改调整函数参数

    简单记录一下利用python装饰器来调整函数的方法.现在有个需求:参数line范围为1-16,要求把9-16的范围转化为1-8,即9对应1,10对应2,...,16对应8. 下面是例子: def fo ...

  10. Maven使用第三方jar文件的两种方法

    转于http://blog.csdn.net/youhaodeyi/article/details/1729116 主要用于回查与标记 在Maven中,使用第三方库一般是通过pom.xml文件中定义的 ...