面試官:count(*)、count1(1)、count(主鍵)、count(字段) 哪個更快?
大家好,我是君哥。
使用 SQL 時,統計表數據量是經常遇到的需求,比如在商品頁面展示商品庫存量。
統計表數據量的方法有 count(*)、count1(1)、count(主鍵 id)、count(字段)。那這些統計方式哪個執行最快呢?以 MySQL 數據庫為參考,今天來聊一聊這個話題。
count(*)
首先 ,count(*) 返回的是檢索到的行數,無論數據行里是否包含空值。
如果使用的是 MyISAM 存儲引擎,MyISAM 會把表的數據行數保存下來存到磁盤,因此執行 count(*) 的時候不用統計,而是直接把保存的結果查出來,效率最高。但這有幾個前提條件:
- 不能帶 where 條件;
- 只能從單表查詢;
- 查詢 SQL 不能有額外的字段;
- MyISAM 不支持事務,可能會有數據不一致的情況。
如果使用的是 InnoDB 存儲引擎,count 就是一個聚合函數,對返回的結果集逐行進行判斷,只要不是 NULL 就加 1,效率不如 MyISAM。當然,MySQL 對 count(*) 做了優化,會選擇數據量最小的二級索引進行掃描,以提高執行效率。
count(1)
對于 count(1) 來說,InnoDB 引擎遍歷整張表,server 層對于返回的每一行,不用取值,直接放一個數值 1 進去,然后計數值加 1。
count(主鍵 id)
對于 count(主鍵 id) ,InnoDB 引擎會遍歷整張表,把每一行的 id 值取出來,返回給 server 層。server 層拿到 id 后,判斷主鍵 id 不可能為空,計數值加 1。
count(字段)
對于 count(字段) 來說:如果字段定義成 not null,server 層逐行判斷,只要引擎層返回的記錄不為空,計數值加 1;如果這個字段定義成 null,那server 層拿到記錄后,還需要取出值判斷是不是 null,如果值不是 null,計數值加 1。
小結
對于 InnoDB 引擎來說,使用 count(*) 和 count(1) 是一樣的,沒有性能區別。下面是官網解釋。
InnoDB handles SELECT COUNT(*) and SELECT COUNT(1) operations in the same way. There is no performance difference.
count(*) 比 count(主鍵 id) 快,因為執行 count(主鍵 id) 的時候,引擎層需要解析主鍵 id 的值返回給 server 層。
count(字段) 性能最差,因為引擎層解析字段值返回給 server 層后,server 層需要從行記錄取出值進行判斷是否為 NULL。
緩存
那有更快的方案查詢表數據量嗎?有。可以考慮使用 redis 緩存來。在 redis 中建 一個 key 來保存數據量,當表插入新數據時,緩存數據量 + 1。
但使用緩存可能會有兩個問題:
- 會有數據不一致得情況,不適用于精確統計的場景;
比如上圖,插入數據后,還沒有來得及更新緩存,已經有應用查詢了緩存的 count 值。
可以考慮把緩存的 count 值寫入 MySQL 數據庫,使用 InnoDB 引擎的事務來保證一致性。
2. redis 服務宕機后,會丟失數據。這個可以在 redis 重啟后重新從數據庫查詢最新 count 數來寫入緩存。
總結
- 如果數據量不大,比如不到百萬級別,或者對 count 值精確度要求很高,可以直接使用 count(*) 獲取行數。
- 如果數據量非常大,查詢頻率也很高,可以考慮緩存 count 值;
- 如果考慮使用緩存,但業務又要求 count 值精確,那就把 count 值緩存在數據庫中;
- 如果對 count 精確度要求不高,可以考慮把 count 值緩存在 redis;
- 如果要統計某一個字段非空的行數,則使用 count(字段);
- 如果是分庫分表的場景,則采用并行統計來提高效率。