MySQL中的 count(*) 真的要比 count(1) 快嗎?以下這篇文章跟大家比較一下MySQL中的 count(*) 和 count(1),看看它們的效能差距,希望對大家有幫助!
今天有人跟我講 MySQL 中count(1)
比count(*)
快,這能忍?必須得和他掰扯掰扯。
宣告:以下討論是基於 InnoDB 儲存引擎,MyISAM 因為情況特殊我在文末會單獨說一下。 【相關推薦:mysql影片教學】
先說結論:這兩個效能差異不大。
我準備了一張有 100W 個資料的表,表結構如下:
CREATE TABLE `user` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(255) DEFAULT NULL, `address` varchar(255) DEFAULT NULL, `password` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
可以看到,有一個主鍵索引。
我們來用兩種方式統計表中的記錄數,如下:
#可以看到,兩個SQL 的執行效率其實差不多,都是0.14s。
再來看另外兩個統計:
id 是主鍵,username 以及 address 則是普通欄位。
可以看出,用 id 來統計,也有一丟丟優勢。松哥這裡因為測試資料樣板比較小,所以效果不明顯,小夥伴可以加大測試資料量,那麼這種差異會更加明顯。
那麼到底是什麼原因造成的這種差異,接下來我們就來簡單分析一下。
我們先用explain 來看下這幾個SQL 不同的執行計劃:
可以看到,前三個統計方式的執行計劃是一樣的,後面兩個是一樣的。
我這裡跟大家比較下explain 中的不同項:
透過explain 我們其實也能大概看出來前三種統計方式的執行效率是要高一些的(因為用到了索引),而後面兩種的統計效率相對來說要低一些的(沒用索引,需要全表掃描)。
只有上面的分析還不夠,我們再來從原理角度來分析一下。
3.1 主鍵索引與普通索引
在開始原理分析以前,我想先帶領大家來看看B 樹,這對我們理解接下來的內容有重要作用。
大家都知道,InnoDB 中索引的儲存結構都是B 樹(至於什麼是B 樹,和B 樹有什麼差別,這個本文就不討論了,這兩個單獨都能整出來一文章),主鍵索引和普通索引的儲存又有所不同,如下圖表示主鍵索引:
可以看到,在主鍵索引中,葉子結點保存了每一行的數據。
而在普通索引中,葉子結點保存的是主鍵值,當我們使用普通索引去搜尋資料的時候,先在葉子結點中找到主鍵,再拿著主鍵去主鍵索引中查找數據,相當於做了兩次查找,這也就是我們平常所說的回表操作。
3.2 原理分析
不知道夥伴們有沒有註意過,我們學習MySQL 的時候,count 函數是歸在聚合函數那一類的,就是avg、sum 等,count 函數和這些歸在一起,說明它也是一個聚合函數。
既然是聚合函數,那麼就需要對傳回的結果集進行一行行的判斷,這裡就牽涉到一個問題,回傳的結果是啥?我們分別來看:
對於select count(1) from user;
這個查詢來說,InnoDB 引擎會去找到一個最小的索引樹去遍歷(不一定是主鍵索引) ,但不會讀取數據,而是讀到一個葉子節點,就回傳1,最後將結果累積。
對於select count(id) from user;
這個查詢來說,InnoDB 引擎會遍歷整個主鍵索引,然後讀取id 並返回,不過因為id 是主鍵,就在B 樹的葉子節點上,所以這個過程不會涉及到隨機IO(不需要回表等操作去資料頁拿資料),效能也是OK 的。
對於select count(username) from user;
這個查詢來說,InnoDB 引擎會遍歷整個表做全表掃描,讀取每一行的username 欄位並傳回,如果username在定義時候設定了not null,那麼直接統計username 的個數;如果username 在定義的時候沒有設定not null,那麼就先判斷一下username 是否為空,然後再統計。
最後再來說select count(*) from user;
,這個SQL 的特別之處在於它被MySQL 優化過,當MySQL 看到count(*)
就知道你是想統計總記錄數,就會去找一個最小的索引樹去遍歷,然後統計記錄數。
因為主鍵索引(聚集索引)的葉子節點是數據,而普通索引的葉子節點則是主鍵值,所以普通索引的索引樹要小一些。然而在上文的案例中,我們只有主鍵索引,所以最後使用的就是主鍵索引。
現在,如果我修改上面的表,為username 欄位也新增索引,然後我們再來看explain select count(*) from user;
的執行計劃:
可以看到,此時使用的索引就是username 索引了,和我們前面的分析結果是一致的。
從上面的描述我們就可以看出,第一個查詢效能最高,第二個次之(因為需要讀取id 並返回),第三個最差(因為需要全表掃描),第四個的查詢效能則接近第一個。
可能有小夥伴知道,MyISAM 引擎中的select count(*) from user;
操作執行起來是非常快的,那是因為MyISAM 把表中的行數直接存在磁碟中了,需要的時候直接讀取出來就行了,所以非常快。
MyISAM 引擎之所以這樣做,主要是因為它是不支援交易的,所以它的統計實際上就非常容易,添加一行記錄一行就行了。
而我們常用的 InnoDB 卻不能這樣做!為啥?因為 InnoDB 支援事務!為了支援事務,InnoDB 引入了MVCC 多版本並發控制,所以在資料讀取的時候可能會有髒讀、幻讀以及不可重複讀等問題,
##具體可以參考:https: //www.bilibili.com/video/BV14L4y1B7mB所以,InnoDB 需要將每一行數據拿出來,判斷該行數據對當前會話是否可見,如果可見,就統計該行數據,否則不予統計。 當然,MySQL 中的 MVCC 其實是一個很宏大的話題,松哥以後有空了再和大家詳細介紹 MVCC。 好啦,現在小夥伴們懂了吧?有問題歡迎留言討論。 更多程式相關知識,請造訪:
程式設計影片! !
以上是分析一下MySQL中的 count(*) 真的要比 count(1) 快嗎?的詳細內容。更多資訊請關注PHP中文網其他相關文章!