MYSQL COUNT(*) 和 COUNT(col_name) 的差異問題
此問題我的工作夥伴在使用
COUNT(*)後,解決了速度緩慢問題,另外他也有點無法理解為什麼這麼做速度變快...我的想法和實驗的結果跟他有些許落差,故在這裡做個筆記,若日後回顧需再額外驗證正確性...
因為這件事情,LINE群都快炸了...感謝他救了所有人
問題發生
今天晚上收到來自某個客戶的親切問候,抱怨某個有分頁列表的UI跑得十分緩慢(7秒)...
問題分析
- 這個UI畫面總共有2個SQL敘述所組成
- 取得列表資料,沒有Where條件,有limit 和 offset 敘述,有order by 敘述(依日期降冪),沒有group by敘述
- 取得總筆數數值,沒有Where條件,沒有limit 和 offset 敘述,沒有order by 敘述,沒有group by敘述僅單純使用
SELECT COUNT(Product_Name) - 資料庫引擎 InnoDB
- 資料庫總筆數約320萬筆左右
- 目前已知影響速度的問題是來自計算總筆數的SQL敘述(4~5秒)
- 我的工作夥伴使用
COUNT(*)敘述後,速度降為1秒~1.3左右秒
原有想法
從我接觸的MySQL的時候,在我的認知裡,COUNT(column_name)一定會比COUNT(*)敘述快,主要的想法是COUNT(*)敘述
會吐出所有列的資料,而COUNT(column_name)則是吐出指定列的資料,所以就以邏輯來說COUNT(column_name)應該會是比COUNT(*)快。
想法思考
基於這個想法,我嘗試將SELECT COUNT(Product_Name)改成其他欄位的名稱,經測試,意外發現除了幾個有經過索引的欄位速度快以外,其他沒有經過索引的欄位,速度就跟原本的速度依樣都是約(4~5秒),可是這樣為何COUNT(*)速度會變快呢?
這是猜測(未經驗證)的想法
按照先前的測試,在使用COUNT函數有索引的欄位會比沒有索引的快,這個是能理解並且有邏輯的,假設欄位有使用索引,便會使用索引來計算行數,如果沒有索引,將不得不讀取數據頁以檢查列的NULL值然後計算行數,那COUNT(*)速度為什麼有時候會比COUNT(column_name)快,COUNT(*)操作被優化以快速計算表中的總行數,而不需要逐一檢查每個列的值。這種優化使得COUNT(*)在許多情況下,特別是使用InnoDB引擎時,比COUNT(column_name)更加高效,因為後者需要檢查指定列的每一個非NULL值。然而,如果COUNT(column_name)操作指定的列被索引,那麼性能可能得到提升,但通常不會超過COUNT(*)的性能,因為MySQL優化器會選擇最有效的計算方式。
COUNT函數速度慢的問題,似乎僅發生在InnoDB引擎上,MyISAM引擎則不會有這個問題,MyISAM引擎把一個表的總行數存在了硬碟上,因此執行count(*)的時候會直接返回這個內容
後續
這是另外發生事情,雖然我的工作夥伴解決掉速度緩慢的問題,但是在部屬前的單元測試未通過,我檢查了一下主要是忘記了一些COUNT的特性
* COUNT(*)會計算表中所有的算表中的所有行,不管任何列中的NULL值,全部都會計算
* COUNT(col_name) 計算指定列中非NULL值的數量。它專注於一個特定的列,並忽略指定列中有NULL值的行。
例子
SELECT * from `sales`;
+-----------+---------------------+--------------+
| ID | product | revenue |
+-----------+---------------------+--------------+
| 1 | Widget A | 150 |
| 2 | Widget B | NULL |
| 3 | Widget A | 200 |
| 4 | Widget C | 180 |
| 5 | Widget B | 120 |
| 6 | Widget A | NULL |
+-----------+---------------------+--------------+
有一個sales的資料表,記錄了銷售的資料,包括產品名稱(product)和銷售額(revenue),但有些銷售額信息是NULL值。