Mysql性能優化:爲何count(*)這麼慢?

導讀

  • 在開發中必定會用到統計一張表的行數,好比一個交易系統,老闆會讓你天天生成一個報表,這些統計信息少不了sql中的count函數。
  • 可是隨着記錄愈來愈多,查詢的速度會愈來愈慢,爲何會這樣呢?Mysql內部究竟是怎麼處理的?
  • 今天這篇文章將從Mysql內部對於count函數是怎樣處理的來展開詳細的講述。

count的實現方式

  • 在Mysql中的不一樣的存儲引擎對count函數有不一樣的實現方式。
  • MyISAM引擎把一個表的總行數存在了磁盤上,所以執行count(*)的時候會直接返回這個數,效率很高(沒有where查詢條件)。
  • InnoDB引擎並無直接將總數存在磁盤上,在執行count(*)函數的時候須要一行一行的將數據讀出來,而後累計總數。

爲何InnoDB不將總數存起來?

  • 說到InnoDB相信讀者總會想到其支持事務的特性,事務具備隔離性,若是將總數存起來,怎麼保證各個事務之間的總數的一致性呢?不明白的看下圖:

  • 事務A事務B中的count(*)的執行結果是不一樣的,所以InnoDB引擎在每一個事務中返回多少行是不肯定的,只能一行一行的讀出來用來判斷總數。

如何提高count效率

  • InnoDB對於如何提高count(*)的查詢效率,網上有多種解決辦法,這裏主要介紹三種,並分析可行性。

show table status

  • show table status這個命令可以很快的查詢出數據庫中每一個表的行數,可是真的可以替代count(*)嗎?redis

  • 答案是不能。緣由很簡單,這個命令統計出來的值是一個「估值」,所以是不許確的,官方文檔說偏差大概在40%-50%sql

  • 所以這種方法直接pass,不許確還用它幹嗎。數據庫

 

緩存系統存儲總數

  • 這種方法也是最容易想到的,增長一行就+1,刪除一行就-1,而且緩存系統讀取也是很快,既簡單又方便的爲何不用?緩存

  • 緩存系統和Mysql是兩個系統,好比redisMysql這兩個是典型的比較。兩個系統最難的就是在高併發下沒法保證數據的一致性。經過如下兩圖咱們來理解一下:併發

  • 經過上面兩張圖,不管是redis計數+1仍是insert into user先執行,最終都會致使數據在邏輯上的不一致。第一張圖會出現redis計數少了,第二張圖雖然計數正確了可是並無查詢出插入的那一行數據。
  • 在併發系統裏面,咱們是沒法精確控制不一樣線程的執行時刻的,由於存在圖中的這種操做序列,因此,咱們說即便Redis正常工做,這個計數值仍是邏輯上不精確的。

在數據庫保存計數

  • 經過緩存系統保存的分析得知了使用緩存沒法保證數據在邏輯上的一致性,所以咱們想到了直接使用數據庫來保存,有了「事務」的支持,也就保證了數據的一致性了。
  • 如何使用呢?很簡單,直接將計數保存在一張表中(table_name,total)。
  • 至於執行的邏輯只須要將緩存系統中redis計數+1改爲total字段+1便可,以下圖:

  • 因爲在同一個事務中,保證了數據在邏輯上的一致性。

不一樣count的用法

  • count()是一個聚合函數,對於返回的結果集,一行行地判斷,若是count函數的參數不是NULL,累計值就加1,不然不加。最後返回累計值。
  • count的用法有多種,分別是count(*)、count(字段)、count(1)、count(主鍵id)。那麼多種用法,到底有什麼差異呢?固然,「前提是沒有where條件語句」。
  • count(id):InnoDB引擎會遍歷整張表,把每一行的id值都取出來,返回給server層。server層拿到id後,判斷是不可能爲空的,就按行累加。
  • count(1):InnoDB引擎遍歷整張表,但不取值。server層對於返回的每一行,放一個數字1進去,判斷是不可能爲空的,按行累加。
  • count(字段):
  • 若是這個「字段」是定義爲not null的話,一行行地從記錄裏面讀出這個字段,判斷不能爲null,按行累加;
  • 若是這個字段定義容許爲null,那麼執行的時候,判斷到有多是null,還要把值取出來再判斷一下,不是null才累加。
  • count(*):不會把所有字段取出來,而是專門作了優化,不取值。count(*)確定不是null,按行累加。
  • 因此結論很簡單:「按照效率排序的話,count(字段)<count(主鍵id)<count(1)≈count(*),因此建議讀者,儘可能使用count(*)。」
  • 「注意」:這裏確定有人會問,count(id)不是走的索引嗎,爲何查詢效率和其餘的差很少呢?陳某在這裏解釋一下,雖然走的索引,可是仍是要一行一行的掃描才能統計出來總數。

總結

  • MyISAM表雖然count(*)很快,可是不支持事務;
  • show table status命令雖然返回很快,可是不許確;
  • InnoDB直接count(*)會遍歷全表(沒有where條件),雖然結果準確,但會致使性能問題。
  • 緩存系統的存儲計數雖然簡單效率高,可是沒法保證數據的一致性。
  • 數據庫保存計數很簡單,也能保證數據的一致性,建議使用。  
相關文章
相關標籤/搜索