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. 冒泡排序和用for循环画菱形

    忘了当时刚开始学java编程时提到的冒泡排序和for循环画菱形怎么做了, 找了找以前的练习, 重新修改了一遍, 其实冒泡排序也是可以排列字符串和字符的, package com.test; publi ...

  2. 复制本地文件到HDFS本地测试异常

    项目中需要将本地文件拷贝到hdfs上,由于本人比较懒,于是使用擅长的Java程序通过Hadoop.FileSystem.CopyFromLocalFile方法来实现. 在本地(Window 7 环境) ...

  3. caffe net 可视化工具

    http://ethereon.github.io/netscope/#/editor 将对应的网络输入到里面,然后按shift+enter即可查看对应的网络结构

  4. ibatis 批量插入oracle总结

    1. 使用批量插入最先想到如下的插入语句 insert into a (id,name) values('','') ,('','') 但是 oracle 并不支持这种写法 然后查询得知可以使用如下写 ...

  5. Config文件的读写

    using System; using System.Configuration; using System.Xml; namespace COMMON { public class ConfigHe ...

  6. 拼sql条件时判断 是不是当前时间是不是周五,如果今天不是周五,就选上周五

    if (Request.QueryString["start"] == null) { for (int i = 0; i < 6; i++) { if (DateTime. ...

  7. 【OpenGL】如何绘制Shadow

    背景 Shadow即阴影,它是光线被不透明物体遮挡而产生的黑暗区域,与光源的方向相反. 在Blender中编辑过程中没有Shadow,只有在经过渲染后才能显示.目前有一个基于Blender的项目,要求 ...

  8. 1293. 3n+1数链问题 2016 12 23

    /* 1293. 3n+1数链问题 Constraints Time Limit: 1 secs, Memory Limit: 32 MB Description 在计算机科学上,有很多类问题是无法解 ...

  9. js 变量提升和函数提升原理

    关于js的变量,开始的时候我们都会被告知,变量声明应该在引用该变量之前.关于为什么要这样做呢,开始的时候本着会用就行的目的,也没去深究.不过后来经常会发现一些让人很费解的..姑且称为现象吧.先看一段代 ...

  10. oracle中用户删除不了,ORA-01940提示 “无法删除当前已连接用户”

    Oracle删除用户的提示无法删除当前已连接用户两种解决方法如下: 1.先锁定用户.然后查询进程号,最后删除对应的进程.在删除对应的用户 SQL>alter user XXX account l ...