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