面試官:count(*)、count1(1)、count(主鍵)、count(字段) 哪個(gè)更快?
大家好,我是君哥。
使用 SQL 時(shí),統(tǒng)計(jì)表數(shù)據(jù)量是經(jīng)常遇到的需求,比如在商品頁(yè)面展示商品庫(kù)存量。
統(tǒng)計(jì)表數(shù)據(jù)量的方法有 count(*)、count1(1)、count(主鍵 id)、count(字段)。那這些統(tǒng)計(jì)方式哪個(gè)執(zhí)行最快呢?以 MySQL 數(shù)據(jù)庫(kù)為參考,今天來(lái)聊一聊這個(gè)話題。
count(*)
首先 ,count(*) 返回的是檢索到的行數(shù),無(wú)論數(shù)據(jù)行里是否包含空值。
如果使用的是 MyISAM 存儲(chǔ)引擎,MyISAM 會(huì)把表的數(shù)據(jù)行數(shù)保存下來(lái)存到磁盤,因此執(zhí)行 count(*) 的時(shí)候不用統(tǒng)計(jì),而是直接把保存的結(jié)果查出來(lái),效率最高。但這有幾個(gè)前提條件:
- 不能帶 where 條件;
- 只能從單表查詢;
- 查詢 SQL 不能有額外的字段;
- MyISAM 不支持事務(wù),可能會(huì)有數(shù)據(jù)不一致的情況。
如果使用的是 InnoDB 存儲(chǔ)引擎,count 就是一個(gè)聚合函數(shù),對(duì)返回的結(jié)果集逐行進(jìn)行判斷,只要不是 NULL 就加 1,效率不如 MyISAM。當(dāng)然,MySQL 對(duì) count(*) 做了優(yōu)化,會(huì)選擇數(shù)據(jù)量最小的二級(jí)索引進(jìn)行掃描,以提高執(zhí)行效率。
count(1)
對(duì)于 count(1) 來(lái)說(shuō),InnoDB 引擎遍歷整張表,server 層對(duì)于返回的每一行,不用取值,直接放一個(gè)數(shù)值 1 進(jìn)去,然后計(jì)數(shù)值加 1。
count(主鍵 id)
對(duì)于 count(主鍵 id) ,InnoDB 引擎會(huì)遍歷整張表,把每一行的 id 值取出來(lái),返回給 server 層。server 層拿到 id 后,判斷主鍵 id 不可能為空,計(jì)數(shù)值加 1。
count(字段)
對(duì)于 count(字段) 來(lái)說(shuō):如果字段定義成 not null,server 層逐行判斷,只要引擎層返回的記錄不為空,計(jì)數(shù)值加 1;如果這個(gè)字段定義成 null,那server 層拿到記錄后,還需要取出值判斷是不是 null,如果值不是 null,計(jì)數(shù)值加 1。
小結(jié)
對(duì)于 InnoDB 引擎來(lái)說(shuō),使用 count(*) 和 count(1) 是一樣的,沒(méi)有性能區(qū)別。下面是官網(wǎng)解釋。
InnoDB handles SELECT COUNT(*) and SELECT COUNT(1) operations in the same way. There is no performance difference.
count(*) 比 count(主鍵 id) 快,因?yàn)閳?zhí)行 count(主鍵 id) 的時(shí)候,引擎層需要解析主鍵 id 的值返回給 server 層。
count(字段) 性能最差,因?yàn)橐鎸咏馕鲎侄沃捣祷亟o server 層后,server 層需要從行記錄取出值進(jìn)行判斷是否為 NULL。
緩存
那有更快的方案查詢表數(shù)據(jù)量嗎?有。可以考慮使用 redis 緩存來(lái)。在 redis 中建 一個(gè) key 來(lái)保存數(shù)據(jù)量,當(dāng)表插入新數(shù)據(jù)時(shí),緩存數(shù)據(jù)量 + 1。
但使用緩存可能會(huì)有兩個(gè)問(wèn)題:
- 會(huì)有數(shù)據(jù)不一致得情況,不適用于精確統(tǒng)計(jì)的場(chǎng)景;

比如上圖,插入數(shù)據(jù)后,還沒(méi)有來(lái)得及更新緩存,已經(jīng)有應(yīng)用查詢了緩存的 count 值。
可以考慮把緩存的 count 值寫入 MySQL 數(shù)據(jù)庫(kù),使用 InnoDB 引擎的事務(wù)來(lái)保證一致性。
2. redis 服務(wù)宕機(jī)后,會(huì)丟失數(shù)據(jù)。這個(gè)可以在 redis 重啟后重新從數(shù)據(jù)庫(kù)查詢最新 count 數(shù)來(lái)寫入緩存。
總結(jié)
- 如果數(shù)據(jù)量不大,比如不到百萬(wàn)級(jí)別,或者對(duì) count 值精確度要求很高,可以直接使用 count(*) 獲取行數(shù)。
- 如果數(shù)據(jù)量非常大,查詢頻率也很高,可以考慮緩存 count 值;
- 如果考慮使用緩存,但業(yè)務(wù)又要求 count 值精確,那就把 count 值緩存在數(shù)據(jù)庫(kù)中;
- 如果對(duì) count 精確度要求不高,可以考慮把 count 值緩存在 redis;
- 如果要統(tǒng)計(jì)某一個(gè)字段非空的行數(shù),則使用 count(字段);
- 如果是分庫(kù)分表的場(chǎng)景,則采用并行統(tǒng)計(jì)來(lái)提高效率。



























