記得很早以前就有人跟我說過,在使用count的時候要用count(1)而不要用count(*),因為使用count(*)的時候會對所有的列進行掃描,相比而言count(1)不用掃描所有列,所以count(1)要快一些。當時是對這一結論深信不疑,雖然不知道為什么。今天正好有時間研究研究看count(*)和count(1)到底有沒有性能差異。測試環境是SQL Server 2005 SP2開發版。
在進行測試之前先建立一些測試的數據,代碼如下:
1 create table test(a int, b varchar(100)) 2 go 3 4 declare @n int 5 set @n = 1 6 while @n < 100000 7 begin 8 if @n%3 = 0 9 insert into test values (@n, null) 10 if @n%3 = 1 11 insert into test values (@n, str(@n)) 12 if @n%3 = 2 13 insert into test values (@n, 'this is text') 14 set @n = @n+1 15 end
這里先說明一下,為了測試的目的,test表里面是故意沒有加索引的。
count(*)與count(1)的對比
現在我們開始驗證count(*)和count(1)的區別,驗證方法很簡單,如果兩個語句執行效率不一樣的話它們的查詢計划肯定會不一樣的,我們先執行set showplan_text on打開SQL執行計划顯示,然后我們執行相應的SQL語句。
先是count(*):
select count(*) from test
接着count(1):
select count(1) from test
對比下兩個執行計划我們可以發現是完全一樣的,這也就說明count(*)和count(1)的執行效率是完全一樣的,根本不存在所謂的單列掃描和多列掃描的問題。
count(col)與count(*)的對比
同樣,我們先看一下兩個不同count方式的執行計划。
count(*)的執行計划看上面的例子。
count(b)的執行計划:
select count(b) from test
現在能看到這兩個執行計划唯一不同的地方就是COUNT的內容,對於count(*)是"|—Stream Aggregate(DEFINE:([Expr1005]=count(*)))",對於count(b)是"|—Stream Aggregate(DEFINE:([Expr1005]=COUNT([AdventureWorks].[dbo].[test].[b])))",那這兩種count方式會不會有什么不一樣呢?
讓我們先看一下BOL里面對count(*)以及count(col)的說明:
COUNT(*) 返回組中的項數。包括 NULL 值和重復項。
COUNT(ALL expression) 對組中的每一行都計算 expression 並返回非空值的數量。
expression: 除 text、image 或 ntext 以外任何類型的表達式。不允許使用聚合函數和子查詢。
* :指定應該計算所有行以返回表中行的總數。
COUNT(*) 不需要任何參數,而且不能與 DISTINCT 一起使用。
COUNT(*) 不需要 expression 參數,因為根據定義,該函數不使用有關任何特定列的信息。
COUNT(*) 返回指定表中行數而不刪除副本。它對各行分別計數。包括包含空值的行。
也就是說count(*)只是返回表中行數,因此SQL Server在處理count(*)的時候只需要找到屬於表的數據塊塊頭,然后計算一下行數就行了,而不用去讀取里面數據列的數據。
而對於count(col)就不一樣了,為了去除col列中包含的NULL行,SQL Server必須讀取該col的每一行的值,然后確認下是否為NULL,然后在進行計數。因此count(*)應該是比count(col)快的,下面我們來驗證一下。
我們通過在同樣的條件下,將select count(…) from test執行1000次來看兩種count方式是否是一樣的:
先看count(*)
declare @n int, @a int set @n = 1
while @n <= 1000 begin select @a = count(*) from test set @n = @n+1 end
接着看count(col)
declare @n int, @a int set @n = 1 while @n <= 1000 begin select @a = count(b) from test set @n = @n+1 end
從執行結果可以看出相差還是很大的,count(*)比count(col)快了一倍。
不過因為count(*)和count(col)使用的目的是不一樣的,在必須要使用count(col)的時候還是要用的,只是在統計表全部行數的時候count(*)就是最佳的選擇了。
另外:這里用到的跑1000次的方法也可以用在比較count(*)和count(1)上,在這里你將得到兩個一樣的執行時間。
count(col)與count(distinct col)比較
同樣,我們先對比一下兩個執行計划。
select count(b) from test select count(distinct b) from test
從執行計划我們可以看到,因為表test沒有索引,在執行count(distinct col)的時候是通過Hash Match的方式來查找相同值的行,這顯然會耗費大量的CPU,同時我們也可以知道count(col)能比count(distinct col)快很多的。(如果test的列b有索引的話count(distinct col)的方式會不一樣,走的是group by,但同樣還是會比count(col)慢的,這個大家可以自己試一下)。
我們可以同樣做一個執行1000次看花費的時間來做一個直觀的對比。
declare @n int, @a int set @n = 1 while @n <= 1000 begin select @a = count(b) from test set @n = @n+1 end
declare @n int, @a int set @n = 1 while @n <= 1000 begin select @a = count(distinct b) from test set @n = @n+1 end
索引與count的關系
我們上面討論的都是表的索引結構不變的情況下count的變化,在表索引不變時對表做全表掃描所消耗的IO是不變的,不管是采取那種方式。現在在這里我們將看看不同類型的表索引對count會有什么樣的變化,因為索引結構的改變對IO影響是最大的,在這里我們注重關注IO的變化情況。
先羅列一下我們要用到的SQL語句,包括查看IO,TIME、執行計划以及建立索引的。
-- 打開IO顯示
set statistics io on
-- 打開執行時間顯示
set statistics time on
-- 打開執行計划顯示
set showplan_text on
-- 建立聚集索引pk_test
create clustered index pk_test on test (a)
-- 建立非聚集索引ix_a
create index ix_a on test (a)
-- 建立非聚集索引ix_b
create index ix_b on test (b)
堆表和聚集索引表上的count(*)
在這里我們先取得test沒有建立索引之前執行count(*)的消耗,然后再在test上對a列建立一個聚集索引,然后再看看同樣語句的執行計划和IO。
select count(*) from test
從實際測試我們可以看到,堆表和聚集索引表上的count是沒有什么區別的,甚至於聚集索引表上的IO還要多2(這是因為多了兩個聚集索引的數據塊造成的)。如果你對聚集索引的結構很了解的話也是不難解釋的:其實聚集索引並沒有單獨的保留所有索引列的信息,而只是將表中的行的物理順序按照聚集索引列的順序整理了一下,因此對聚集索引的掃描和對堆表的掃描是一樣的,沒有什么本質上的區別。
因此聚集索引對於count來說是沒有幫助的。
非聚集索引上的count
現在我們執行前面給出的語句為test表增加一個非聚集索引ix_a然后看看執行計划和IO情況。
select count(*) from test
從執行結果可以看到,邏輯讀的次數明顯的減少了,因為計算行數這個操作對於全表掃描或是非聚集索引的掃描結果是一樣的,而相對來說非聚集索引的數據量是肯定會比表的數據量小很多的,同樣的做一次全部掃描所花費的IO也就要少很多了。
同樣的對於一個count(col)的操作來說,對col的索引做count同樣是能達到count(col)的目的的,相比全表掃描一樣可以節省很多的IO操作。
select count(a) from test
結論
這里把上面實驗的結果總結一下:
- count(*)和count(1)執行的效率是完全一樣的。
- count(*)的執行效率比count(col)高,因此可以用count(*)的時候就不要去用count(col)。
- count(col)的執行效率比count(distinct col)高,不過這個結論的意義不大,這兩種方法也是看需要去用。
- 如果是對特定的列做count的話建立這個列的非聚集索引能對count有很大的幫助。
- 如果經常count(*)的話則可以找一個最小的col建立非聚集索引以避免全表掃描而影響整體性能。
- 在不加WHERE限制條件的情況下,COUNT(*)與COUNT(COL)基本可以認為是等價的;
但是在有WHERE限制條件的情況下,COUNT(*)會比COUNT(COL)快非常多; -
count(0)=count(1)=count(*)
1. count(指定的有效值)--執行計划都會轉化為count(*)2. 如果指定的是列名,會判斷是否有null,null不計算