Select count(*)、Count(1)、Count(0)的區別和執行效率比較

 

前言

      記得很早以前就有人跟我說過,在使用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不計算

當然,在建立優化count的索引之前一定要考慮新建立的索引會不會對別的查詢有影響,影響有多大,要充分考慮之後再決定是否要這個索引,這是很重要的一點,不要撿了芝麻丟了西瓜。

轉載於https://www.cnblogs.com/sueris/p/6650301.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章