深入剖析MySQL中COUNT(id)和COUNT(*)哪個效率更高
前言
開發(fā)工作中統(tǒng)計行數(shù),經(jīng)常會寫出兩種寫法:
SELECT COUNT(*) FROM `user_login_log` WHERE `date` = CURDATE(); SELECT COUNT(id) FROM `user_login_log` WHERE `date` = CURDATE();
網(wǎng)上充斥大量老舊傳言:COUNT(*) 需要掃描整行數(shù)據(jù),COUNT(id) 只讀取主鍵,COUNT(id) 速度更快。
這條說法放到現(xiàn)在 InnoDB 引擎下是錯誤謠言。
本文結(jié)合 InnoDB 底層原理,講清楚 COUNT(*)、COUNT(id)、COUNT(普通字段) 的差異,給出生產(chǎn)環(huán)境標準編碼規(guī)范。
前置環(huán)境:本文全部基于 MySQL InnoDB(5.7 / 8.0,線上最通用);MyISAM 機制不一樣,文末單獨說明。
一、先搞懂三個COUNT語法的語義
1. COUNT(*)
SQL標準定義:統(tǒng)計滿足查詢條件的所有行數(shù),不做任何 NULL 判斷。
MySQL官方專門對 COUNT(*) 做優(yōu)化,優(yōu)化器會選擇當(dāng)前表體積最小的二級索引進行掃描計數(shù),不需要讀取聚簇索引完整行數(shù)據(jù)。
2. COUNT(id)
語義:統(tǒng)計 id IS NOT NULL 的記錄行數(shù)。
一般業(yè)務(wù)中 id 為主鍵,主鍵字段強制非空,所以邏輯等價統(tǒng)計總行數(shù)。
執(zhí)行邏輯:掃描索引,讀取主鍵id的值,判斷不為NULL后計數(shù)。
3. COUNT(普通業(yè)務(wù)字段)
COUNT(login_ip)
語義:統(tǒng)計 login_ip IS NOT NULL 的記錄。
風(fēng)險兩點:
- 如果字段允許NULL,統(tǒng)計結(jié)果和真實行數(shù)不一致,產(chǎn)生業(yè)務(wù)BUG;
- InnoDB需要取出字段真實值判斷NULL,開銷高于 COUNT(*)。
二、核心結(jié)論(InnoDB)
帶WHERE條件統(tǒng)計時,COUNT(*) 和 COUNT(id) 性能幾乎沒有差距。
不要耗費精力糾結(jié)二者選擇,二者執(zhí)行計劃、掃描行數(shù)、IO開銷基本持平。
底層原因:InnoDB二級索引葉子節(jié)點本身就存放主鍵id。
無論優(yōu)化器選擇二級索引掃描計數(shù):
- COUNT(*):只需要計數(shù)索引條目,不需要讀取字段值
- COUNT(id):除了計數(shù),還要額外取出id值做非空判斷
理論上 COUNT(*) 會略微優(yōu)于 COUNT(id),只是絕大多數(shù)場景差距感知不到。
三、誤區(qū)拆解:為什么會流傳 COUNT(id) 更快?
謠言來源大多是老舊MyISAM認知混淆,以及早期網(wǎng)絡(luò)文章以訛傳訛:
- MyISAM無WHERE條件
COUNT(*)超快,引擎緩存總行數(shù);但MyISAM早已不是主流; - 很多人主觀猜想:
*代表讀取整行數(shù)據(jù),實際上MySQL優(yōu)化器根本不會讀取完整行; - 沒有區(qū)分「有無WHERE條件」,籠統(tǒng)下定論。
重點糾正:InnoDB中,COUNT(*) 不會讀取完整一行數(shù)據(jù),優(yōu)化器只利用索引條目數(shù)量統(tǒng)計。
四、無WHERE條件的特殊場景
-- 查詢整張表總條數(shù) SELECT COUNT(*) FROM `user`; SELECT COUNT(id) FROM `user`;
很多人發(fā)現(xiàn)這條SQL查詢很慢。
原因:InnoDB事務(wù)多版本機制,沒有辦法緩存表總行數(shù),無論 COUNT(*) / COUNT(id) 都必須掃描索引統(tǒng)計,二者速度依舊基本一致。
想要高頻查詢表總量提速:使用Redis緩存、定時統(tǒng)計表總數(shù),避免頻繁COUNT掃描索引。
五、新增對比:COUNT(常量)
額外拓展一個寫法:
SELECT COUNT(1) FROM `user_login_log` WHERE `date` = CURDATE();
在新版本MySQL中,COUNT(1) 會被優(yōu)化器等價優(yōu)化成 COUNT(*),性能同樣持平。
不用盲目推崇COUNT(1)。
六、一張表清晰區(qū)分三種寫法
| 寫法 | 作用 | 是否判NULL | 性能建議 | 風(fēng)險 |
|---|---|---|---|---|
| COUNT(*) | 統(tǒng)計所有符合條件行 | 不判斷NULL | ?推薦,官方標準 | 無 |
| COUNT(id) | 統(tǒng)計id不為NULL的行 | 判斷NULL | 可用,略遜于COUNT(*) | id必須為主鍵非空,否則結(jié)果異常 |
| COUNT(login_ip) | 統(tǒng)計login_ip不為NULL的行 | 判斷NULL | ?不推薦 | 字段存在NULL時統(tǒng)計數(shù)量失真,開銷更大 |
七、線上編碼規(guī)范建議
優(yōu)先使用 COUNT(*),遵循SQL標準,語義清晰,MySQL官方推薦;
-- 標準寫法 SELECT COUNT(*) AS active_num FROM user_login_log WHERE `date` = CURDATE();
禁止使用 COUNT(普通業(yè)務(wù)字段) 統(tǒng)計表總行數(shù);
如果需要統(tǒng)計「某字段不為空」的數(shù)據(jù),才使用 COUNT(字段名);
-- 合理場景:統(tǒng)計有登錄IP的用戶 SELECT COUNT(login_ip) FROM user_login_log WHERE `date` = CURDATE();
不要為了“優(yōu)化性能”把 COUNT(*) 強行改成 COUNT(id),屬于無效優(yōu)化;
大表頻繁全量COUNT統(tǒng)計,使用緩存預(yù)聚合方案。
八、實戰(zhàn)驗證方式
使用EXPLAIN對比兩條SQL執(zhí)行計劃:
EXPLAIN SELECT COUNT(*) FROM user_login_log WHERE `date` = CURDATE(); EXPLAIN SELECT COUNT(id) FROM user_login_log WHERE `date` = CURDATE();
觀察輸出:type、key、rows基本完全一致,可以直觀證明性能差距極小。
九、補充:MyISAM簡要區(qū)分(了解即可)
MyISAM引擎內(nèi)部保存表總行數(shù):
SELECT COUNT(*) FROM `user`; -- 不加WHERE,瞬間返回
但只要帶上WHERE條件,MyISAM同樣需要掃描數(shù)據(jù),此時 COUNT(*) 與 COUNT(id) 同樣差距不大。
新項目基本不會使用MyISAM,僅作知識拓展。
十、全文總結(jié)
- InnoDB引擎下,
COUNT(*)與COUNT(id)性能幾乎持平,COUNT(*)理論小幅領(lǐng)先; - 網(wǎng)傳「COUNT(id)速度更快」屬于過時謠言,不要作為優(yōu)化依據(jù);
- 統(tǒng)計滿足條件全部行數(shù),統(tǒng)一使用
COUNT(*); - 杜絕用
COUNT(普通字段)統(tǒng)計總行數(shù),存在邏輯BUG與性能損耗; - SQL優(yōu)化把重心放在索引設(shè)計,不要在COUNT寫法上做無效內(nèi)卷。
寫代碼記住一條準則:先保證語義準確,再追求性能;符合SQL標準的 COUNT(*) 是兼顧可讀性與性能的最優(yōu)選擇。
以上就是深入剖析MySQL中COUNT(id)和COUNT(*)哪個效率更高的詳細內(nèi)容,更多關(guān)于MySQL COUNT(id)和COUNT(*)對比的資料請關(guān)注腳本之家其它相關(guān)文章!
- MySQL?count(*),count(id),count(1),count(字段)區(qū)別
- MySQL千萬級數(shù)據(jù)count(*)查詢太慢怎么辦?優(yōu)化技巧分享
- 一篇徹底吃透MySQL中count(*)、count(1)、count(字段)的區(qū)別(不踩坑)
- MySQL中count(*)深度解析與性能優(yōu)化實踐案例
- MySQL中COUNT函數(shù)的使用小結(jié)
- MySQL COUNT用法終極指南:(*)/(1)/(列名)哪個更高效
- Mysql?COUNT()函數(shù)基本用法及應(yīng)用詳解
- mysql count(*)分組之后IFNULL無效問題
相關(guān)文章
MySQL報錯1118,數(shù)據(jù)類型長度過長問題及解決
在使用MySQL過程中,常見的一個問題是報錯1118,這通常發(fā)生在創(chuàng)建表時,錯誤提示為“Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual2024-10-10
MySQL中實現(xiàn)行列轉(zhuǎn)換的操作示例
在 MySQL 中進行行列轉(zhuǎn)換(即,將某些列轉(zhuǎn)換為行或?qū)⒛承┬修D(zhuǎn)換為列)通常涉及使用條件邏輯和聚合函數(shù),本文給大家介紹了MySQL中實現(xiàn)行列轉(zhuǎn)換的操作示例,文中有詳細的代碼示例供大家參考,需要的朋友可以參考下2024-06-06
MySQL數(shù)據(jù)庫常用操作技巧總結(jié)
這篇文章主要介紹了MySQL數(shù)據(jù)庫常用操作技巧,結(jié)合實例形式總結(jié)分析了mysql查詢、存儲過程、字符串截取、時間、排序等常用操作技巧,需要的朋友可以參考下2018-03-03
MySQL入門(二) 數(shù)據(jù)庫數(shù)據(jù)類型詳解
這個數(shù)據(jù)庫所遇到的數(shù)據(jù)類型今天統(tǒng)統(tǒng)在這里講清楚了,以后在看到什么數(shù)據(jù)類型,咱度應(yīng)該認識,對我來說,最不熟悉的應(yīng)該就是時間類型這塊了。但是通過今天的學(xué)習(xí),已經(jīng)解惑了。下面就跟著我的節(jié)奏去把這個拿下吧2018-07-07
window10下mysql 8.0.20 安裝配置方法圖文教程
這篇文章主要為大家詳細介紹了window10下mysql 8.0.20 安裝配置方法圖文教程,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-05-05

