今天遇到某人在我之前写的一篇文章里问到html
若是统计信息没来得及更新的话,那岂不是统计出来的数据时错误的了web
这篇文章的地址:SQLSERVER是怎麽经过索引和统计信息来找到目标数据的(第三篇)ide
以前我觉得SELECT COUNT(*)是根据统计信息来的,可是后来想了一下,这个确定不是性能
那么SQLSERVER怎麽统计SELECT COUNT(*)的呢??测试
其实SQLSERVER也是使用扫描的方法优化
你们也能够先看一下:SQLSERVER中的ALLOCATION SCAN和RANGE SCANspa
可是这里不讨论是ALLOCATION SCAN仍是RANGE SCAN,你们知道SQLSERVER使用的是扫描的方式就能够了3d
汇集索引表code
SQL脚本以下:orm
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 23 24 --查询数据 25 SELECT * FROM ct1
看一下执行计划
(图片一)
1 SET STATISTICS PROFILE ON 2 GO 3 SELECT COUNT(*) FROM [dbo].[ct1]
(图片二)
这里须要了解流聚合运算符
MSDN对于流聚合运算符的解释
(图片三)
宋沄剑的文章里也有对流聚合运算符的解释
重点是理解: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 count(3) 3 SELECT count(c2)
(图片八)
上面三个SQL语句都是按照汇集索引的第一个字段(ct1表中的c1列)来进行统计的
由于汇集索引的第一个字段是根据创建汇集索引的时候的排序顺序预先排好序
Stream Aggregate 运算符要求输入的数据要按某列进行排序
因此不管是指定字段名、*仍是数字,都是根据汇集索引的第一个字段来统计
堆表
SQL脚本以下:
1 CREATE TABLE t1(c1 INT, c2 VARCHAR (8000)); 2 GO 3 4 5 --插入测试数据 6 7 8 9 DECLARE @a INT; 10 SELECT @a = 1; 11 WHILE (@a <= 12) 12 BEGIN 13 INSERT INTO t1 VALUES (@a, replicate('a', 5000)) 14 SELECT @a = @a + 1 15 END 16 GO 17 18 19 20 --查询数据 21 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 --插入测试数据 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 --查询数据 17 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] 19
(图片十六)
你们必定要记住:非汇集索引是创建在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查找运算符
(图片十七)
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补充:
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) ,这样的速度最快!!