自從你們對於MySQL數據庫的穩定性有了更高的追求後,常常有小夥伴有這樣的疑問,對於count(*)這樣的操做,有沒有正確的姿式,或者有沒有能夠優化的地方?mysql
但答案比較殘酷,若是已經使用了正確的索引,那麼基本上沒有能夠優化的地方。一旦出現慢查詢了,它就是慢查詢了,要改,只能本身計數或者經過其餘搜索平臺來作。git
今天,就一塊兒來看看爲何會這樣,並對你們平常會遇到的一些的困惑進行解答。github
聽說,MyISAM 引擎把一個表的總行數存在了磁盤上,所以執行 count(*) 的時候會直接返回這個數,效率很高。web
而咱們的mysql通常都是用Innodb的引擎,Innodb是怎麼實現count操做的呢?面試
InnoDB 引擎就比較麻煩來,它執行 count(*) 的時候,須要把數據一行一行地從引擎裏面讀出來,而後累積計數。sql
因此,當咱們的表裏面的記錄愈來愈多的時候,count(*)就會愈來愈慢。數據庫
固然,咱們這裏說的都是不帶where條件的,若是帶上where條件的話,MyISAM也是很慢的。函數
嗯,首先仍是說,mysql上不太推薦用count(*)來作統計相關業務,尤爲是表很是大的狀況下。性能
那若是業務比較小,須要快速上馬,那麼,至少應該保證count(*)帶上了科學的where條件,而後,這個表也已經創建了科學的索引。優化
那對於統計類的業務,推薦的幾種作法:
有同窗在平常使用過程當中,問可否使用 系統表的統計信息 來代替count。
答案是不行。這裏的tableRows只是一個參考值。
這裏的表統計信息,其實是使用show table status獲取的。這個值是如何獲得的呢?咱們須要瞭解下mysql的採樣統計方法。
爲何要採樣統計呢?由於把整張表取出來一行行統計,雖然能夠獲得精確的結果,可是代價過高了,因此只能選擇「採樣統計」。(因此其實mysql本身也沒有count(*)的好方法)
採樣統計的時候,InnoDB 默認會選擇 N 個數據頁,統計這些頁面上的不一樣值,獲得一個平均值,而後乘以這個索引的頁面數,就獲得了這個索引的基數。
而數據表是會持續更新的,索引統計信息也不會固定不變。因此,當變動的數據行數超過 1/M 的時候,會自動觸發從新作一次索引統計。
所以,這個採樣估算得來的值,是很不許的。有多不許呢,官方文檔說偏差可能達到 40% 到 50%。
在看一些老代碼查詢的時候,咱們常常會看到count(1),count(id),count(字段)等方式,那它們糾結孰優孰劣,到底有沒有性能上的差別呢?
這裏,咱們先要弄清楚 count() 的語義。
count() 是一個聚合函數,對於返回的結果集,一行行地判斷,若是 count 函數的參數不是 NULL,累計值就加 1,不然不加。最後返回累計值。
InnoDB 引擎會遍歷整張表,把每一行的 id 值都取出來,返回給 server 層。server 層拿到 id 後,判斷是不可能爲空的,就按行累加。
InnoDB 引擎遍歷整張表,但不取值。server 層對於返回的每一行,放一個數字「1」進去,判斷是不可能爲空的,按行累加。
若是這個「字段」是定義爲 not null 的話,一行行地從記錄裏面讀出這個字段,判斷不能爲 null,按行累加;
若是這個「字段」定義容許爲 null,那麼執行的時候,判斷到有多是 null,還要把值取出來再判斷一下,不是 null 才累加。
並不會把所有字段取出來,而是專門作了優化,不取值。count(*) 確定不是 null,按行累加。
因此結論是:按照效率排序的話,count(字段)<count(主鍵 id)<count(1)≈count(*),因此我建議,儘可能使用 count(*)。
都看到最後了,原創不易,點個關注,點個贊吧~
知識碎片從新梳理,構建Java知識圖譜: https://github.com/saigu/JavaKnowledgeGraph (歷史文章查閱很是方便)
掃碼關注個人公衆號「阿丸筆記」,第一時間獲取最新更新。同時能免費獲取海量Java技術棧電子書、各個大廠面試題哦。