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

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

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

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

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

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

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

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

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


聚集索引表

SQL脚本如下:

 USE [pratice]
GO --建立聚集索引表
CREATE TABLE ct1(c1 INT, c2 VARCHAR (2000));
GO
--建立聚集索引
CREATE CLUSTERED INDEX t1c1 ON ct1(c1);
GO --插入测试数据
DECLARE @a INT;
SELECT @a = 1;
WHILE (@a <= 12)
BEGIN
INSERT INTO ct1 VALUES (@a, replicate('a', 2000))
SELECT @a = @a + 1
END
GO --查询数据
SELECT * FROM ct1

看一下执行计划

(图片一)

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

(图片二)

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

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

(图片三)

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

SQL Server中的执行引擎入门

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

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

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

(图片四)

(图片五)

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

为什麽要使用流聚合?

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

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

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

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

(图片六)

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

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

(图片七)

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

答案就在

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

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

 SELECT  COUNT(*)
SELECT count(3)
SELECT count(c2)

(图片八)

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

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

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

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


堆表

SQL脚本如下:

 CREATE TABLE t1(c1 INT, c2 VARCHAR (8000));
GO --插入测试数据 DECLARE @a INT;
SELECT @a = 1;
WHILE (@a <= 12)
BEGIN
INSERT INTO t1 VALUES (@a, replicate('a', 5000))
SELECT @a = @a + 1
END
GO --查询数据
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表,脚本如下:

 CREATE TABLE t1(c1 VARCHAR (2), c2 VARCHAR (8000));
GO --插入测试数据
DECLARE @a INT;
SELECT @a = 1;
WHILE (@a <= 12)
BEGIN
INSERT INTO t1 VALUES ('a', replicate('a', 5000))
SELECT @a = @a + 1
END
GO --查询数据
SELECT * FROM t1

结果是

(图片十四)

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

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

(图片十五)


非聚集索引表

SQL脚本如下:

 CREATE TABLE nct1(c1 INT, c2 VARCHAR (8000));
GO
--建立非聚集索引
CREATE INDEX nt1c1 ON nct1(c1);
GO --插入数据
DECLARE @a INT;
SELECT @a = 1;
WHILE (@a <= 10)
BEGIN
INSERT INTO nct1 VALUES (@a, replicate('a', 5000))
SELECT @a = @a + 1
END
GO --查询数据
SELECT * FROM [dbo].[nct1]

(图片十六)

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

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

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

 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]为什麽会用表扫描呢?

 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

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

聚集索引表

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

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

堆表

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

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

非聚集索引表

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

2014-6-21补充:

USE [sss]
--建表
CREATE TABLE counttb ( id INT NULL ) --插入数据
INSERT INTO [dbo].[counttb]
( [id] )
SELECT 1
UNION ALL
SELECT NULL --统计行数
SELECT COUNT(1) ,
COUNT(*) ,
COUNT(id)
FROM [dbo].[counttb] --查询索引的统计值
SELECT a.[rowcnt] ,
b.[name]
FROM sys.[sysindexes] AS a
INNER JOIN sys.[objects] AS b ON a.[id] = b.[object_id]
WHERE b.[name] = 'counttb' --创建非聚集索引
CREATE INDEX ix_counttb_id ON [dbo].[counttb] (id) --统计行数
SELECT COUNT(1) ,
COUNT(*) ,
COUNT(id)
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. 由Struts return SUCCESS引发的基础问题

    该问题的最初来源,是源于Struts中的 return SUCCESS; 和 return "success"; 在Struts的配置文件struts.xml我们可以找到" ...

  2. C#读取Excel的三种方式以及比较

    (1)OleDB方式 优点:将Excel直接当做数据源处理,通过SQL直接读取内容,读取速度较快. 缺点:读取数据方式不够灵活,无法直接读取某一个单元格,只有将整个Sheet页读取出来后(结果为Dat ...

  3. discuz!安装遇到问题的解决方案

    正常的安装步骤好多地方都有写过了,我安装的时候遇到问题百度翻了个遍也没有找到,现在问题已经解决了,发出了分享一下! 进入第三步创建数据库的时候提示:由于目标计算机积极拒绝,无法连接. 打开phpmya ...

  4. js ShowDialogModal 关闭子页面并刷新父页面,保留查询条件

    不知道大家有没有碰到类似的问题,当时的你是什么思路来处理这个问题呢?是url,session,cookie,还是…… 今天笔者就遇到了这个问题,当时的想法如:url,session,cookie都尝试 ...

  5. PHP与memcache安装使用说明

    最近网站流量上来后,数据库连接数一直偏高,分析了下,都是正常请求,只是网站功能分的细,单页面数据库查询句偏多了,很多数据是没必要实时查询,缓存起来就可以的!考虑必须用memcache缓存了,减轻mys ...

  6. factory service provide自定义服务

    1.factory factory , 就是你提供一个方法, 该方法返回一个对象的实例, 对于 AngularJS 的 factory 来说, 就是先定义一个对象, 给这个对象添加属性和方法, 然后返 ...

  7. PHP使用命名空间:别名/导入(Aliasing/Importing)

    1.导入,就是使用use操作符 2.在一个类中导入了另一个类之后,当前的命名空间仍然是当前类的命名空间 3.注意对命名空间中的名称(包含命名空间分隔符的完全限定名称如 Foo\Bar以及相对的不包含命 ...

  8. three.js 根据png生成heightmap

    Three.js: render real world terrain from heightmap using open data By jos.dirksen on Tue, 07/17/2012 ...

  9. ajax select option 数据。为了下次方便信手拈来!!

    为了下次方便信手拈来!! 示例1 var form = document.forms["maddraddform"]; $(form.province).change(functi ...

  10. text-shadow文字阴影属性用法

    text-shadow:offset-x:阴影水平移动,负值时向左偏移 text-shadow:offset-y:阴影垂直移动,负值时向上移动 text-shadow:radio-bluer:阴影到实 ...