MySQL 是使用 COUNT(id) 还是 COUNT(*) 效率更高?
2026/7/25 6:53:19 网站建设 项目流程

标签:MySQL、InnoDB、COUNT优化、索引原理、SQL规范

前言

开发工作中统计行数,经常会写出两种写法:

SELECTCOUNT(*)FROM`user_login_log`WHERE`date`=CURDATE();SELECTCOUNT(id)FROM`user_login_log`WHERE`date`=CURDATE();

网上充斥大量老旧传言:COUNT(*)需要扫描整行数据,COUNT(id)只读取主键,COUNT(id)速度更快。
这条说法放到现在InnoDB 引擎下是错误谣言

本文结合 InnoDB 底层原理,讲清楚COUNT(*)COUNT(id)COUNT(普通字段)的差异,给出生产环境标准编码规范。

前置环境:本文全部基于 MySQL InnoDB(5.7 / 8.0,线上最通用);MyISAM 机制不一样,文末单独说明。

一、先搞懂三个COUNT语法的语义

1. COUNT(*)

SQL标准定义:统计满足查询条件的所有行数,不做任何 NULL 判断。
MySQL官方专门对COUNT(*)做优化,优化器会选择当前表体积最小的二级索引进行扫描计数,不需要读取聚簇索引完整行数据。

2. COUNT(id)

语义:统计id IS NOT NULL的记录行数。
一般业务中id为主键,主键字段强制非空,所以逻辑等价统计总行数。
执行逻辑:扫描索引,读取主键id的值,判断不为NULL后计数。

3. COUNT(普通业务字段)

COUNT(login_ip)

语义:统计login_ip IS NOT NULL的记录。
⚠️ 风险两点:

  1. 如果字段允许NULL,统计结果和真实行数不一致,产生业务BUG;
  2. InnoDB需要取出字段真实值判断NULL,开销高于 COUNT(*)。

二、核心结论(InnoDB)

带WHERE条件统计时,COUNT(*) 和 COUNT(id) 性能几乎没有差距。
不要耗费精力纠结二者选择,二者执行计划、扫描行数、IO开销基本持平。

底层原因:
InnoDB二级索引叶子节点本身就存放主键id。
无论优化器选择二级索引扫描计数:

  • COUNT(*):只需要计数索引条目,不需要读取字段值
  • COUNT(id):除了计数,还要额外取出id值做非空判断

理论上COUNT(*)略微优于 COUNT(id),只是绝大多数场景差距感知不到。

三、误区拆解:为什么会流传 COUNT(id) 更快?

谣言来源大多是老旧MyISAM认知混淆,以及早期网络文章以讹传讹:

  1. MyISAM无WHERE条件COUNT(*)超快,引擎缓存总行数;但MyISAM早已不是主流;
  2. 很多人主观猜想:*代表读取整行数据,实际上MySQL优化器根本不会读取完整行;
  3. 没有区分「有无WHERE条件」,笼统下定论。

重点纠正:
InnoDB中,COUNT(*) 不会读取完整一行数据,优化器只利用索引条目数量统计。

四、无WHERE条件的特殊场景

-- 查询整张表总条数SELECTCOUNT(*)FROM`user`;SELECTCOUNT(id)FROM`user`;

很多人发现这条SQL查询很慢。
原因:InnoDB事务多版本机制,没有办法缓存表总行数,无论COUNT(*)/COUNT(id)都必须扫描索引统计,二者速度依旧基本一致。

想要高频查询表总量提速:使用Redis缓存、定时统计表总数,避免频繁COUNT扫描索引。

五、新增对比:COUNT(常量)

额外拓展一个写法:

SELECTCOUNT(1)FROM`user_login_log`WHERE`date`=CURDATE();

在新版本MySQL中,COUNT(1)会被优化器等价优化成COUNT(*),性能同样持平。
不用盲目推崇COUNT(1)。

六、一张表清晰区分三种写法

写法作用是否判NULL性能建议风险
COUNT(*)统计所有符合条件行不判断NULL✅推荐,官方标准
COUNT(id)统计id不为NULL的行判断NULL可用,略逊于COUNT(*)id必须为主键非空,否则结果异常
COUNT(login_ip)统计login_ip不为NULL的行判断NULL❌不推荐字段存在NULL时统计数量失真,开销更大

七、线上编码规范建议

  1. 优先使用 COUNT(*),遵循SQL标准,语义清晰,MySQL官方推荐;
-- 标准写法SELECTCOUNT(*)ASactive_numFROMuser_login_logWHERE`date`=CURDATE();
  1. 禁止使用 COUNT(普通业务字段)统计表总行数;
  2. 如果需要统计「某字段不为空」的数据,才使用COUNT(字段名)
-- 合理场景:统计有登录IP的用户SELECTCOUNT(login_ip)FROMuser_login_logWHERE`date`=CURDATE();
  1. 不要为了“优化性能”把COUNT(*)强行改成COUNT(id),属于无效优化;
  2. 大表频繁全量COUNT统计,使用缓存预聚合方案。

八、实战验证方式

使用EXPLAIN对比两条SQL执行计划:

EXPLAINSELECTCOUNT(*)FROMuser_login_logWHERE`date`=CURDATE();EXPLAINSELECTCOUNT(id)FROMuser_login_logWHERE`date`=CURDATE();

观察输出:type、key、rows基本完全一致,可以直观证明性能差距极小。

九、补充:MyISAM简要区分(了解即可)

MyISAM引擎内部保存表总行数:

SELECTCOUNT(*)FROM`user`;-- 不加WHERE,瞬间返回

但只要带上WHERE条件,MyISAM同样需要扫描数据,此时COUNT(*)COUNT(id)同样差距不大。
新项目基本不会使用MyISAM,仅作知识拓展。

十、全文总结

  1. InnoDB引擎下,COUNT(*)COUNT(id)性能几乎持平,COUNT(*)理论小幅领先;
  2. 网传「COUNT(id)速度更快」属于过时谣言,不要作为优化依据;
  3. 统计满足条件全部行数,统一使用COUNT(*)
  4. 杜绝用COUNT(普通字段)统计总行数,存在逻辑BUG与性能损耗;
  5. SQL优化把重心放在索引设计,不要在COUNT写法上做无效内卷。

写代码记住一条准则:先保证语义准确,再追求性能;符合SQL标准的COUNT(*)是兼顾可读性与性能的最优选择。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询