count(*) 的實現方式
討論的是沒有過濾條件的 count(*)
在不同的 MySQL 引擎中,count(*)
有不同的實現方式。
MyISAM 引擎:
把一個表的總行數存在了磁盤上,直接返回這個數,效率很高;
InnoDB 引擎:
把數據一行一行地從引擎里面讀出來(走最小的樹的葉子節點的鏈表),然后累積計數。
為什么要使用 InnoDB,因為不論是在事務支持、并發能力還是在數據安全方面,InnoDB 都優于 MyISAM
為什么InnoDB 不記錄總行數
因為要支持事務
多版本并發控制MVCC, 每一行記錄都要判斷自己是否對這個會話可見,
因此對于 count(*) 請求來說,InnoDB 只好把數據一行一行地讀出依次判斷,可見的行才能夠用于計算“基于這個查詢”的表的總行數。
會找到最小的那棵樹來遍歷
InnoDB 是索引組織表,主鍵索引樹的葉子節點是數據,而普通索引樹的葉子節點是主鍵值。
所以,普通索引樹比主鍵索引樹小很多。
對于 count(*) 這樣的操作,遍歷哪個索引樹得到的結果邏輯上都是一樣的。
因此,MySQL 優化器會找到最小的那棵樹來遍歷。
在保證邏輯正確的前提下,盡量減少掃描的數據量,是數據庫系統設計的通用法則之一。
如果自己來記錄行數
用redis來記不行: 1. 崩潰丟失 2.并發, 如果取行數的同時 改數據 會不準確
用一個表來記錄行數
會話A 在插入行, 會話B在讀取行并且count, 因為事務隔離, A的修改對B就沒影響, B自己是一致的
count(*)、count(主鍵 id)、count(字段) 和 count(1) 比較
count(字段)<count(主鍵 id)<count(1)≈count(*)
盡量使用 count(*)
count(主鍵 id) : InnoDB 引擎會遍歷整張表,把每一行的 id 值都取出來,返回給 server 層。server 層拿到 id 后,判斷是不可能為空的,就按行累加。
count(1) : InnoDB 引擎遍歷整張表,但不取值。server 層對于返回的每一行,放一個數字“1”進去,判斷是不可能為空的,按行累加。
count(字段):如果這個“字段”是定義為 not null 的話,一行行地從記錄里面讀出這個字段,判斷不能為 null,按行累加;如果這個“字段”定義允許為 null,那么執行的時候,判斷到有可能是 null,還要把值取出來再判斷一下,不是 null 才累加。
count(*)
: 專門做了優化,不取值。count(*)
肯定不是 null,按行累加。
分析性能差別的時候,原則:
- server 層要什么就給什么;
- InnoDB 只給必要的值;
現在的優化器只優化了 count(*) 的語義為“取行數”,其他“顯而易見”的優化并沒有做。也就是前面的第一條原則,server 層要什么字段,InnoDB 就返回什么字段。