今天遇到某人在我以前寫的一篇文章里問到
如果統(tǒng)計(jì)信息沒來(lái)得及更新的話,那豈不是統(tǒng)計(jì)出來(lái)的數(shù)據(jù)時(shí)錯(cuò)誤的了
這篇文章的地址:SQLSERVER是怎麼通過索引和統(tǒng)計(jì)信息來(lái)找到目標(biāo)數(shù)據(jù)的(第三篇)
之前我以為SELECT COUNT(*)是根據(jù)統(tǒng)計(jì)信息來(lái)的,但是后來(lái)想了一下,這個(gè)肯定不是
那么SQLSERVER怎麼統(tǒng)計(jì)SELECT COUNT(*)的呢??
其實(shí)SQLSERVER也是使用掃描的方法
大家也可以先看一下:SQLSERVER中的ALLOCATION SCAN和RANGE SCAN
但是這里不討論是ALLOCATION SCAN還是RANGE SCAN,大家知道SQLSERVER使用的是掃描的方式就可以了
聚集索引表
SQL腳本如下:
看一下執(zhí)行計(jì)劃
(圖片一)
1 SET STATISTICS PROFILE ON2 GO3 SELECT COUNT(*) FROM [dbo].[ct1]
(圖片二)
這里需要了解流聚合運(yùn)算符
MSDN對(duì)于流聚合運(yùn)算符的解釋
(圖片三)
宋沄劍的文章里也有對(duì)流聚合運(yùn)算符的解釋
重點(diǎn)是理解:Stream Aggregate 運(yùn)算符按一列或多列對(duì)行分組,然后計(jì)算由查詢返回的一個(gè)或多個(gè)聚合表達(dá)式
Stream Aggregate 運(yùn)算符按一列對(duì)行分組,然后計(jì)算由查詢返回的一個(gè)聚合表達(dá)式
我們用下面兩個(gè)圖會(huì)清楚一些
(圖片四)
(圖片五)
SQLSERVER對(duì)表中的行分組進(jìn)行掃描,但是SQLSERVER以多少行為一組來(lái)進(jìn)行掃描呢??這個(gè)不得而知了
為什麼要使用流聚合?
大家一定會(huì)自然而然地想到分組統(tǒng)計(jì)提高性能,特別是表中數(shù)據(jù)量非常大的時(shí)候,分組統(tǒng)計(jì)特別有用
計(jì)算標(biāo)量運(yùn)算符只是把聚合的結(jié)果隱式轉(zhuǎn)換為int類型
大家知道ct1表只有兩列,但是SELECT COUNT(3) FROM [dbo].[ct1]也能夠返回表中的行數(shù)
1 SELECT COUNT(1) FROM [dbo].[ct1]
1 SELECT COUNT(3) FROM [dbo].[ct1]
(圖片六)
就算用列名都是一樣的執(zhí)行計(jì)劃
1 SELECT COUNT(c1) FROM [dbo].[ct1]2 SELECT COUNT(c2) FROM [dbo].[ct1]
(圖片七)
SQLSERVER究竟以哪一列來(lái)進(jìn)行表的行數(shù)統(tǒng)計(jì)的呢??????
答案就在
Stream Aggregate 運(yùn)算符要求輸入的數(shù)據(jù)要按某列進(jìn)行排序,如果由于前面的 Sort 運(yùn)算符或已排序的索引查找或掃描導(dǎo)致數(shù)據(jù)尚未排序,
則優(yōu)化器將在此運(yùn)算符前面使用一個(gè) Sort 運(yùn)算符,使表的某列是有序排序的。
1 SELECT COUNT(*)2 SELECT count(3)3 SELECT count(c2)
(圖片八)
上面三個(gè)SQL語(yǔ)句都是按照聚集索引的第一個(gè)字段(ct1表中的c1列)來(lái)進(jìn)行統(tǒng)計(jì)的
因?yàn)榫奂饕牡谝粋€(gè)字段是根據(jù)建立聚集索引的時(shí)候的排序順序預(yù)先排好序
Stream Aggregate 運(yùn)算符要求輸入的數(shù)據(jù)要按某列進(jìn)行排序
所以無(wú)論是指定字段名、*還是數(shù)字,都是根據(jù)聚集索引的第一個(gè)字段來(lái)統(tǒng)計(jì)
堆表
SQL腳本如下:
(圖片九)
(圖片十)
堆表這里使用的是ALLOCATION SCAN
因?yàn)榉峙漤?yè)面的時(shí)候是根據(jù)c1列的值從1~12進(jìn)行分配的
(圖片十一)
109頁(yè)面存放的c1值是1
120頁(yè)面存放的c1值是2
174頁(yè)面存放的c1值是3
193頁(yè)面存放的c1值是4
8316頁(yè)面存放的c1值是5
8340頁(yè)面存放的c1值是6
8351頁(yè)面存放的c1值是7
8353頁(yè)面存放的c1值是8
。
。
。
。
。
(圖片十二)
這里執(zhí)行計(jì)劃在流聚合之前并沒有進(jìn)行排序的原因:因?yàn)榻ū磉M(jìn)行頁(yè)面分配的時(shí)候已經(jīng)按照C1列的值進(jìn)行有序的頁(yè)面分配
所以當(dāng)ALLOCATION SCAN的時(shí)候,C1列已經(jīng)是有序的了
(圖片十三)
不明白的童鞋可以再看一下:SQLSERVER中的ALLOCATION SCAN和RANGE SCAN
為什麼SQLSERVER選擇統(tǒng)計(jì)C1列的值,因?yàn)镃1列的值是可以排序的,C2列不能排序,統(tǒng)計(jì)不了
那么如果一個(gè)表中沒有可以用來(lái)排序的列呢????
先drop掉t1表,再建立t1表,腳本如下:
結(jié)果是
(圖片十四)
我覺得SQLSERVER應(yīng)該會(huì)在表中加上一列,類似用來(lái)區(qū)分聚集索引頁(yè)面重復(fù)值的UNIQUIFIER(KEY)列
當(dāng)查詢完畢之后就刪除掉這一列
(圖片十五)
非聚集索引表
SQL腳本如下:
(圖片十六)
大家一定要記住:非聚集索引是建立在c1列上的!!!
下面兩個(gè)SQL語(yǔ)句都是一樣的,都是根據(jù)c1列的值進(jìn)行統(tǒng)計(jì),而SQLSERVER只掃描非聚集索引頁(yè)面,而不掃描數(shù)據(jù)頁(yè)面
1 SELECT COUNT(*) FROM [dbo].[nct1]2 3 SELECT COUNT(3) FROM [dbo].[nct1]
SELECT COUNT(*) FROM [dbo].[nct1]是不需要到數(shù)據(jù)頁(yè)面去讀取c2列的數(shù)據(jù)的,只需要掃描非聚集索引頁(yè)面(c1列)就可以了
SELECT COUNT(3) FROM [dbo].[nct1]跟SELECT COUNT(*) FROM [dbo].[nct1]也是一樣
不知道大家還記得書簽查找不,如果SQLSERVER掃描了非聚集索引頁(yè)面之后還需要到數(shù)據(jù)頁(yè)面去讀取其他字段的數(shù)據(jù)的話,就需要RID查找運(yùn)算符
(圖片十七)
SELECT COUNT(*) FROM [dbo].[nct1]和SELECT COUNT(3) FROM [dbo].[nct1]的掃描方式跟前面說(shuō)的聚集索引表是差不多的
這里就不一一敘述了~
而SELECT COUNT(c2) FROM [dbo].[nct1]為什麼會(huì)用表掃描呢?
1 SELECT COUNT(c2) FROM [dbo].[nct1]
c2列不在非聚集索引頁(yè)面里,所以需要表掃描
(圖片十八)
SELECT COUNT(c2) FROM [dbo].[nct1]跟前面說(shuō)的堆表是差不多的,這里就不一一敘述了
總結(jié)
做了這麼多實(shí)驗(yàn)
可以總結(jié)出:select count(*)、count(數(shù)字)、count(字段名)是沒有性能差別的??!
我說(shuō)的沒有差別是在相同的條件下,就像非聚集索引表,如果使用
SELECT COUNT(c2) FROM [dbo].[nct1]
跟SELECT COUNT(*) FROM [dbo].[nct1]、SELECT COUNT(3) FROM [dbo].[nct1]相比肯定有差別
因?yàn)?strong>SELECT COUNT(c2) FROM [dbo].[nct1]走的是表掃描
如果SELECT COUNT(c1) FROM [dbo].[nct1]
跟SELECT COUNT(*) FROM [dbo].[nct1]、SELECT COUNT(3) FROM [dbo].[nct1]相比是沒有差別的
(圖片十九)
大家走的都是非聚集索引掃描
無(wú)論是聚集索引表、堆表、非聚集索引表都是掃描表中的記錄來(lái)統(tǒng)計(jì)出表中的行數(shù)的
希望大家看完這篇文章之后,不再一知半解了,這是我的希望o(∩_∩)o
如有不對(duì)的地方,歡迎大家拍磚o(∩_∩)o
-----------------------------------------------------------------------
補(bǔ)上IO和時(shí)間的比較 2013-10-19
---------------------------------
聚集索引表
1 SET STATISTICS IO ON2 SET STATISTICS TIME ON3 GO4 SELECT COUNT(*) FROM [dbo].[ct1]
1 SQL Server 分析和編譯時(shí)間: 2 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 2 毫秒。3 4 (1 行受影響)5 表 'ct1'。掃描計(jì)數(shù) 1,邏輯讀取 5 次,物理讀取 0 次,預(yù)讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預(yù)讀 0 次。6 7 SQL Server 執(zhí)行時(shí)間:8 CPU 時(shí)間 = 15 毫秒,占用時(shí)間 = 2 毫秒。
1 SET STATISTICS IO ON2 SET STATISTICS TIME ON3 GO4 SELECT COUNT(1) FROM [dbo].[ct1]
1 SQL Server 分析和編譯時(shí)間: 2 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 2 毫秒。3 4 (1 行受影響)5 表 'ct1'。掃描計(jì)數(shù) 1,邏輯讀取 5 次,物理讀取 0 次,預(yù)讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預(yù)讀 0 次。6 7 SQL Server 執(zhí)行時(shí)間:8 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。
1 SET STATISTICS IO ON2 SET STATISTICS TIME ON3 GO4 SELECT COUNT(c1) FROM [dbo].[ct1]
1 SQL Server 分析和編譯時(shí)間: 2 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 1 毫秒。3 4 (1 行受影響)5 表 'ct1'。掃描計(jì)數(shù) 1,邏輯讀取 5 次,物理讀取 0 次,預(yù)讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預(yù)讀 0 次。6 7 SQL Server 執(zhí)行時(shí)間:8 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。
---------------------------------------------------
堆表
1 SET STATISTICS IO ON2 SET STATISTICS TIME ON3 GO4 SELECT COUNT(*) FROM [dbo].[t1]
1 SQL Server 分析和編譯時(shí)間: 2 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。 3 4 SQL Server 執(zhí)行時(shí)間: 5 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。 6 7 SQL Server 執(zhí)行時(shí)間: 8 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。 9 SQL Server 分析和編譯時(shí)間: 10 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。11 12 (1 行受影響)13 表 't1'。掃描計(jì)數(shù) 1,邏輯讀取 12 次,物理讀取 0 次,預(yù)讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預(yù)讀 0 次。14 15 SQL Server 執(zhí)行時(shí)間:16 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。
1 SET STATISTICS IO ON2 SET STATISTICS TIME ON3 GO4 SELECT COUNT(1) FROM [dbo].[t1]
1 SQL Server 分析和編譯時(shí)間: 2 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。 3 4 SQL Server 執(zhí)行時(shí)間: 5 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。 6 7 SQL Server 執(zhí)行時(shí)間: 8 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。 9 SQL Server 分析和編譯時(shí)間: 10 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 79 毫秒。11 12 (1 行受影響)13 表 't1'。掃描計(jì)數(shù) 1,邏輯讀取 12 次,物理讀取 0 次,預(yù)讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預(yù)讀 0 次。14 15 SQL Server 執(zhí)行時(shí)間:16 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。
1 SET STATISTICS IO ON2 SET STATISTICS TIME ON3 GO4 SELECT COUNT(c1) FROM [dbo].[t1]
1 SQL Server 分析和編譯時(shí)間: 2 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。 3 4 SQL Server 執(zhí)行時(shí)間: 5 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。 6 7 SQL Server 執(zhí)行時(shí)間: 8 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。 9 SQL Server 分析和編譯時(shí)間: 10 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 1 毫秒。11 12 (1 行受影響)13 表 't1'。掃描計(jì)數(shù) 1,邏輯讀取 12 次,物理讀取 0 次,預(yù)讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預(yù)讀 0 次。14 15 SQL Server 執(zhí)行時(shí)間:16 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。
-----------------------------------------------------------------------------------------
非聚集索引表
1 SET STATISTICS IO ON2 SET STATISTICS TIME ON3 GO4 SELECT COUNT(*) FROM [dbo].[nct1]
1 SQL Server 分析和編譯時(shí)間: 2 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。 3 4 SQL Server 執(zhí)行時(shí)間: 5 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。 6 7 SQL Server 執(zhí)行時(shí)間: 8 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。 9 SQL Server 分析和編譯時(shí)間: 10 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 1 毫秒。11 12 (1 行受影響)13 表 'nct1'。掃描計(jì)數(shù) 1,邏輯讀取 2 次,物理讀取 0 次,預(yù)讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預(yù)讀 0 次。14 15 SQL Server 執(zhí)行時(shí)間:16 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。
1 SET STATISTICS IO ON2 SET STATISTICS TIME ON3 GO4 SELECT COUNT(1) FROM [dbo].[nct1]
1 SQL Server 分析和編譯時(shí)間: 2 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。 3 4 SQL Server 執(zhí)行時(shí)間: 5 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。 6 7 SQL Server 執(zhí)行時(shí)間: 8 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。 9 SQL Server 分析和編譯時(shí)間: 10 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。11 12 (1 行受影響)13 表 'nct1'。掃描計(jì)數(shù) 1,邏輯讀取 2 次,物理讀取 0 次,預(yù)讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預(yù)讀 0 次。14 15 SQL Server 執(zhí)行時(shí)間:16 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 49 毫秒。
1 SET STATISTICS IO ON2 SET STATISTICS TIME ON3 GO4 SELECT COUNT(c1) FROM [dbo].[nct1]
1 SQL Server 分析和編譯時(shí)間: 2 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。 3 4 SQL Server 執(zhí)行時(shí)間: 5 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。 6 7 SQL Server 執(zhí)行時(shí)間: 8 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。 9 SQL Server 分析和編譯時(shí)間: 10 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 0 毫秒。11 12 (1 行受影響)13 表 'nct1'。掃描計(jì)數(shù) 1,邏輯讀取 2 次,物理讀取 0 次,預(yù)讀 0 次,lob 邏輯讀取 0 次,lob 物理讀取 0 次,lob 預(yù)讀 0 次。14 15 SQL Server 執(zhí)行時(shí)間:16 CPU 時(shí)間 = 0 毫秒,占用時(shí)間 = 1 毫秒。
2014-6-21補(bǔ)充:
USE [sss]--建表CREATE TABLE counttb ( id INT NULL )--插入數(shù)據(jù)INSERT INTO [dbo].[counttb] ( [id] ) SELECT 1 UNION ALL SELECT NULL --統(tǒng)計(jì)行數(shù)SELECT COUNT(1) , COUNT(*) , COUNT(id)FROM [dbo].[counttb]--查詢索引的統(tǒng)計(jì)值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'--創(chuàng)建非聚集索引CREATE INDEX ix_counttb_id ON [dbo].[counttb] (id)--統(tǒng)計(jì)行數(shù)SELECT COUNT(1) , COUNT(*) , COUNT(id)FROM [dbo].[counttb]
因?yàn)樵趧?chuàng)建非聚集索引前和創(chuàng)建非聚集索引后的行數(shù)值都是一樣的,可以看出COUNT(*) COUNT(1) 和COUNT(ID)
的統(tǒng)計(jì)方式不一樣,所以沒有可比性
一般我們?cè)诮y(tǒng)計(jì)行數(shù)的時(shí)候都會(huì)把NULL值統(tǒng)計(jì)在內(nèi)的,所以這樣的話,最好就是使用COUNT(*) 和COUNT(1) ,這樣的速度最快??!
聯(lián)系客服