相信每一個人在寫代碼時都有遇到過要獲取MYSQL表裏數據行數的狀況,多數人獲取數據錶行數時都用COUNT(*)
,但同時也流傳了很多其餘方式,好比說COUNT(1)、COUNT(主鍵)、COUNT(字段)。到底哪一種方式MYSQL執行起來更快也是衆說紛紜,其實以前我也不知道到底哪一個執行起來快,到底誰說的對(笑哭)。好在最近在認真學習極客時間的MySQL專欄,其中專門有一節是對這個問題的討論,看完後也是解除了長久以來的疑惑。併發
文章中都是針對MySQL的InnoDB引擎展開討論的,MyISAM引擎是把一個表的總行數記錄在了磁盤裏,查詢時效率很高(若是加了where條件也不能直接從磁盤返回)。而InnoDB因爲多版本併發控制(MVCC)的緣由,即便時同一時刻的查詢InnoDB表應該"返回多少行"也是不肯定的,好比假設表t中有10000行數據:分佈式
時刻 | 會話A | 會話B | 會話C |
---|---|---|---|
T1 | begin; | ||
T2 | select count(*) from t; | ||
T3 | insert into t (插入一行); | ||
T4 | begin; | ||
T5 | insert into t (插入一行); | ||
T6 | select count(*) from t; (返回10000) | select count(*) from t; (返回10002); | select count(*) from t; (返回10001) |
會話A在T1開啓事務拿到一致性視圖,可重複讀級別下在事務中任什麼時候刻讀到數據都同樣,其餘事務的更新對會話A沒影響因此count(*)
的結果是10000,會話B在T4開啓事務拿到一致性視圖,T4以前會話C已經新插入了一條語句並提交(單獨執行一條更新語句,InnoDB會本身啓動一個事務,語句執行完立刻提交)。會話B在T5插入一條新數據,在T6查詢時count(*)
的結果是10002(T4 begin時會話C insert語句已經提交,因此在會話B的事務中能看到這個更新)。因爲會話B在T6時事務尚未提交,會話C看不到會話B的更新,因此會話C在T6時count(*)
的結果是10001。函數
COUNT是一個聚合函數,它的功能是對返回的結果集中每一行進行判斷,若是COUNT函數的參數不是NULL則累加1,不然不累加,最後返回累計值。接下來看一下每一個COUNT版本的執行效率:學習
COUNT(*)
MySQL專門作了優化,會找到表中最小的索引樹,InnoDB普通索引樹比主鍵索引小不少,對於COUNT(*)
遍歷哪一個樹是同樣的,count(*)
時MySQL不取記錄值,count(*)
也確定不爲NULL,Server層中直接按行累加。因此這個版本COUNT的從低到高分別爲:優化
COUNT(字段)
< COUNT(主鍵)
< COUNT(1)
≈ COUNT(*)
code
因此建議你儘可能使用count(*)
來獲取記錄行數。索引
另外要注意,不少人爲了銷量會把表的行數記錄到Redis中,但這樣不能保證Redis裏的計數和MySQL表裏的數據保持精確一致,這是兩個不一樣的存儲系統不支持分佈式事務因此就沒法拿到精確的一致性視圖,若是爲了效率把錶行數單獨存儲那麼最好存放在一個單獨的MySQL表裏,這樣沒法拿到一致性視圖的問題就能解決了。事務
關於MySQL更詳細的分析,推薦關注MySQL實戰45講get