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

