爲什麼 select count(*) from t,在 InnoDB 引擎中比 MyISAM 慢?

統計一張表的總數量,是我們開發中常有的業務需求,通常情況下,我們都是使用 select count(*) from tSQL 語句來完成。隨着業務數據的增加,你會發現這條語句執行的速度越來越慢,爲什麼它會變慢呢?

爲什麼會變慢?想要得到答案就需要知道 MySQL 是如何統計總數量的,先說一個前提吧,count(*) 的具體實現是由存儲引擎實現的,也就是說不同的存儲引擎實現的方式不一樣。標題:爲什麼select count( * ) from t,在 InnoDB 引擎中比 MyISAM 慢?也是高頻面試題。

InnoDB和MyISAM 是我們常用的 MySQL 存儲引擎,所以主要對比一下 count(*) 在 InnoDB 和 MyISAM 中的實現:

  • 在 MyISAM 存儲引擎中,把表的總行數存儲在磁盤上,當執行 select count(*) from t 時,直接返回總數據
  • 在 InnoDB 存儲引擎中,跟 MyISAM 不一樣,沒有將總行數存儲在磁盤上,當執行 select count(*) from t 時,會先把數據讀出來,一行一行的累加,最後返回總數量

知道了 InnoDB 和 MyISAM 引擎 count(*) 實現之後,爲什麼select count(*) from t,在 InnoDB 引擎中比 MyISAM 慢?應該有答案了吧,但是這個結論需要有一個前提,就是統計 SQL 不帶過濾條件。如果 統計數量 SQL 語句爲:select count(*) from t where x = 23,那麼在 MyISAM 中就不一定比 InnoDB 快了。

InnoDB 中 count(*) 語句是在執行的時候,全表掃描統計總數量,所以當數據越來越大時,語句就越來越耗時了,爲什麼 InnoDB 引擎不像 MyISAM 引擎一樣,將總行數存儲到磁盤上?這跟 InnoDB 的事務特性有關,由於多版本併發控制(MVCC)的原因,InnoDB 表“應該返回多少行”也是不確定的。

不妨用一個例子來說明一下,假設現在 t 表中有 10000 條數據,現在有三個用戶同時訪問的會話:

  • 會話 A 先啓動事務並查詢一次表的總行數。
  • 會話 B 啓動事務,插入一行後記錄後,查詢表的總行數。
  • 會話 C 先啓動一個單獨的語句,插入一行記錄後,查詢表的總行數。

會話執行流程圖

假設從上到下是按照時間順序執行的,同一行語句是在同一時刻執行的。可以看出在最後時刻,三個會話返回的總行數不一樣。

出現不一樣的結果跟 InnoDB 存儲引擎有關係,在默認隔離級別可重複讀的情況下,通過多版本併發控制(MVCC)來實現,每一行記錄都需要判斷自己是否對這個會話可見,因此在統計總數量時,InnoDB 只好把數據一行一行的讀取出來判斷,只有當前會話可見的才納入統計中。所以同一時刻不同會話查詢到的數量就不一樣。

InnoDB 引擎在 count(*)語句上也做了優化,我們知道,在 InnoDB 存儲引擎中是以索引組織表的方式存儲數據,主鍵索引樹上葉子節點存放在所有的數據,而普通索引樹的葉子節點是主鍵值,所以普通索引樹會比主鍵索引樹小很多,但是數量是一樣的,也就是說遍歷主鍵索引樹和普通索引樹得到的結果都是一樣的。MySQL 就利用了這一特性,在 InnoDB 中執行 select count(*) from t 語句時,MySQL 優化器會找到最小的那棵索引樹來遍歷,這樣可能就可以減少加載次數,在一定程度上提升了 count(*)的執行效率。

最後

目前互聯網上很多大佬都有MySQL相關文章,如有雷同,請多多包涵了。原創不易,碼字不易,還希望大家多多支持。若文中有所錯誤之處,還望提出,謝謝。

互聯網平頭哥

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章