MySQL:SELECT COUNT 小結

MySQL:SELECT COUNT 小結

背景

今天團隊在作線下代碼評審的時候,發現同窗們在代碼中出現了select count(1) 、 select count(*),和具體的select count(字段)的不一樣寫法,本着分析的目的在會議室討論了起來,那這幾種寫法究竟孰優孰劣呢,咱們一塊兒來看一下。html

討論概括

先來看看MySQL官方對SELECT COUNT的定義:mysql

傳送門:https://dev.mysql.com/doc/refman/5.6/en/aggregate-functions.html#function_countsql

 大概能夠分下面這幾個步驟討論。數據庫

COUNT(expr)的分析

COUNT(expr)函數返回的值是由SELECT語句檢索的行中expr表達式非null的計數值,一個BIGINT的值。 若是沒有匹配到數據,COUNT(expr)將返回0,一般有下面這三種用法:緩存

一、COUNT(字段) 會統計該字段在表中出現的次數,忽略字段爲null 的狀況。即不統計字段爲null 的記錄。 併發

二、COUNT(*) 則不一樣,它執行時返回檢索到的行數的計數,無論這些行是否包含null值,app

三、COUNT(1)跟COUNT(*)相似,不將任何列是否null列入統計標準,僅用1表明代碼行,因此在統計結果的時候,不會忽略列值爲NULL的行。編輯器

因此執行如下數據會出現這樣的結果(這邊是故意給component字段設置了幾個null值):函數

1 select COUNT(*),COUNT(1),COUNT(component) from worklog;

 

概括以下: 性能

count(*) 包括了全部的列,至關於行數,在統計結果的時候,不會忽略列值爲NULL  
count(1) 包括了忽略全部列,用1表明代碼行,在統計結果的時候,不會忽略列值爲NULL  
count(字段) 只包括字段那一列,在統計結果的時候,會忽略列值爲null的計數,即某個字段值爲NULL時,不統計

 

關於 COUNT(*) 和 COUNT(1)

先看看COUNT(*),MyISAM 引擎會把一個表的總行數記錄了下來,因此在執行 COUNT(*) 的時候會直接返回數量,執行效率很高。對於InnoDB這樣的事務性存儲引擎, 由於增長了版本控制(MVCC)的緣由,同時有多個事務訪問數據而且有更新操做的時候,每一個事務須要維護本身的可見性,那麼每一個事務查詢到的行數也是不一樣的,因此不能緩存具體的行數,他每次都須要 count 計算一下全部的行數。

至於 COUNT(1) 和 COUNT(*)有什麼區別呢,根據官網的內容(即上述截圖倒數第二段),兩種實現上其實同樣:

InnoDB handles SELECT COUNT(*) and SELECT COUNT(1) operations in the same way. There is no performance difference. 

由於COUNT(*) 不care返回值是否爲空都會將改行歸入計算,因此他count了全部行數,而 COUNT(1) 中的 1 ,則是遇到了行的時候爲恆真表達式,因此 COUNT(*) 仍是 COUNT(1) 都是對全部的結果集進行 count,他們本質上沒有什麼區別。姑且認爲 COUNT(*) ≈ COUNT(1)。 

 

關於COUNT(字段)

咱們再來看看的COUNT(字段),他的查詢就簡單粗暴了,就是進行全表掃描,而後判斷拿到的字段的值是否是爲NULL,不爲NULL則累加。

相比COUNT(*),COUNT(字段)多了一個步驟就是判斷所查詢的字段是否爲NULL,因此他的性能要比COUNT(*)和COUNT(1)慢。 

 

總結 

綜上,COUNT(1)和 COUNT(*)表示的是直接查詢符合條件的數據庫表的行數。而COUNT(字段)表示的是查詢符合條件的列的值,並判斷不爲NULL的行數的累計,效率天然會低一點,

除了查詢獲得結果集有區別以外,相比COUNT(1) 和 COUNT(字段)來說,COUNT(*)是SQL92定義的標準統計數的語法,是官方提供的標準方案,基於此,MySQL數據庫對他進行過不少優化。

注:SQL92,是數據庫的一個ANSI/ISO標準。它定義了一種語言(SQL)以及數據庫的行爲(事務、隔離級別等)。

下面是對一張具備3400W數據的表的統計過程,comid是整型,能夠對比下執行效率差別: 

 

  

使用建議

根據總結的內容,從效率層面說,COUNT(*) ≈ COUNT(1) > COUNT(字段),又由於 COUNT(*)是SQL92定義的標準統計數的語法,咱們建議使用 COUNT(*)。

咱們再來看看MySQL數據庫作了哪些優化:以MySQL中比較經常使用的執行引擎InnoDB和MyISAM爲例子。

一、MyISAM不支持事務,MyISAM中的鎖是表級鎖;

由於MyISAM的鎖是表級鎖,因此同一張表上面的操做是串行執行的,MyISAM把表的總行數單獨記錄下來,若是隻是使用COUNT(*)對錶進行查詢的時候,能夠直接返回這個記錄的數值就能夠了。

這樣表中總行數記錄便可提供給COUNT(*)查詢使用,又因MyISAM數據庫是表級鎖,數據庫行數不會被並行修改,因此行數是準確無誤的。

二、InnoDB支持事務,其中大部分操做都是行級鎖。

這樣就不能愉快的作這種緩存操做了,由於表的行數可能會被併發修改,緩存記錄下來的總行數就不許確了。

在InnoDB中,使用COUNT(*)查詢行數的時候,不須要進行掃表,只要獲取記錄行數而已。因此官方在針對InnoDB的 SELECT COUNT(*) FROM 語句執行過程,會自動選擇一個成本較低的索引進行的話,這樣就能夠大大節省時間。

InnoDB中索引分爲聚簇索引(主鍵索引)和非聚簇索引(非主鍵索引),聚簇索引的葉子節點中保存的是整行記錄,而非聚簇索引的葉子節點中保存的是該行記錄的主鍵的值,非聚簇索引要比聚簇索引小不少,MySQL會優先選擇最小的非聚簇索引來掃表,這樣能夠保證COUNT(*)的最優效率。

當查詢語句中包含WHERE以及GROUP BY條件,會有一些其餘的因素影響,因此要綜合考慮。

 

判斷數據在否,COUNT怎麼用?

上面那種很獲取COUNT數的場景多用於數據分頁,數據統計的場景,有不少的狀況則是直接判斷數據是否存在,這種狀況下,實際上是不關心有多少數據。可是咱們CoreReview的時候仍是會很常常看到這種作法:

1 select COUNT(*) from test_ucsyncdetail where comid>520;
2 
3 int count = testDao.CountByComId(comId);
4 if(count>0){
5    //存在,則執行存在分支的代碼
6 }
7 else{
8   //不存在,則執行存在分支的代碼
9 }

更好的寫法應該是這樣:

1 select 1 from test_ucsyncdetail where comid>520 limit 1;
2  
3 Object tda= testDao.checkExit(comId);
4 if(tda != null){
5     //存在,則執行存在分支的代碼
6 }
7  else{
8    //不存在,則執行存在分支的代碼
9 }


規避了SQL使用COUNT表達式掃表的操做,而是改用SELECT 1 ... LIMIT 1,數據庫查詢時遇到一條就返回,不會再繼續查找和執行,若是存在傳輸回一條結果爲1的數據 ,不然爲null,業務代碼中直接判斷是否非空便可

 

後記

細節把握的好很差,真的影響很大,接下來準備從新擼一下 《高性能MySQL》和《MySql筆記》。

相關文章
相關標籤/搜索