COUNT(*)不会漏计NULL行,因为它统计满足WHERE条件的行数,无论字段是否为NULL;而COUNT(列名)只统计该列非NULL的行数,存在漏计风险。

为什么COUNT(*)不会漏计NULL行
COUNT(*)统计的是“行是否存在”,只要满足WHERE条件,哪怕所有字段都是NULL,这一行也会被计入。而COUNT(列名)语义上是“该列值不为NULL的行数”,一旦updated_at、email或score允许为NULL,就天然存在漏计风险。线上表里常有软删除未填字段、异步任务未更新字段、导入数据缺失字段等情况——这些NULL不会报错,但会让COUNT(email)比COUNT(*)少几百甚至几万行。
COUNT(*)能走最小索引扫描,COUNT(列名)没得选
MySQL优化器对COUNT(*)有专用路径:它会自动挑选最窄的二级索引(比如单列status索引)遍历叶子节点,I/O极小;而COUNT(列名)只能用该列上的索引,哪怕它比主键还宽,也必须硬着头皮扫。更糟的是:
- 如果该列没索引,InnoDB只能走聚簇索引,加载整行再提取字段,I/O放大5–10倍
- 如果该列是
VARCHAR(1000)且只建了前缀索引INDEX idx_name (name(10)),COUNT(name)仍需回表取完整值判空,索引完全失效 - 即使该列定义为NOT NULL,优化器也不敢跳过取值流程,多一次解码开销
COUNT(*)是SQL92标准,兼容性不漂移
COUNT(*)在所有MySQL版本、所有存储引擎(InnoDB/MyISAM)、所有分支(包括阿里云RDS定制版)中行为一致。而COUNT(1)虽结果相同,但server层要确认常量1的非空性;COUNT(id)看似安全,一旦主键被误设为可空(极少见但可能),结果就不可靠。某些旧版MySQL曾对COUNT(*)做过深度内联优化,COUNT(1)反而没命中——这种细节你没法靠文档穷举。
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
带WHERE时三者执行计划一样,但语义风险仍在
只要SQL含WHERE(如SELECT COUNT(*) FROM user WHERE status = 'active'),COUNT(*)、COUNT(1)、COUNT(id)的执行计划完全一致,真正影响速度的是WHERE能否走索引。但此时若误用COUNT(email),问题就从“慢”升级为“错”:你本想统计“活跃用户数”,结果统计的是“活跃且填了邮箱的用户数”,业务方根本看不出异常,直到对账出问题。
真正容易被忽略的点是:很多人以为COUNT(*)会“查出所有字段再丢弃”,其实它全程只推进游标、累加计数,连行头都不用解码;而COUNT(列名)哪怕只是TINYINT,也得把值从索引或聚簇索引里取出来,再做一次IS NOT NULL判断——这个动作在百万级表上可能多耗几十毫秒,在高并发统计场景下就是瓶颈。










