count(*)统计行存在性,不读列值;count(col)统计非null列值,需读取并判断每行col是否为null,二者逻辑、性能及结果均不同。

count(*) 统计的是“行存在性”,不是“列值有效性”
这是最根本的差异点。COUNT(*) 只关心某一行是否满足 WHERE 条件(或全表时是否物理存在),不读取任何列内容;而 COUNT(col) 必须读取 col 的实际值,并跳过所有 NULL 值。哪怕 col 是主键、有索引、甚至定义为 NOT NULL,只要执行时某行该列值为 NULL(比如触发器/应用层写入了 NULL,或字段被显式设为 NULL),它就不会被计入。
col 允许 NULL 时,结果必然小于或等于 count(*)
常见错误场景包括:
- 字段定义为
DEFAULT NULL,但未设NOT NULL约束,插入时没给值 → 该行col为NULL - 使用
INSERT ... SELECT或批量导入,源数据含空字符串或NULL,目标列又允许NULL -
UPDATE ... SET col = NULL WHERE ...后忘记清理逻辑,导致部分行 “意外失联”
例如:SELECT COUNT(*) FROM user; 返回 1000,但 SELECT COUNT(email) 可能只返回 950 —— 因为 50 行的 email 列是 NULL,哪怕它们其他字段都完整。
即使 col 是 NOT NULL,count(col) 和 count(*) 的执行逻辑仍不同
很多人看到结果一致就认为二者等价,这是危险的误解:
-
COUNT(*)在 InnoDB 中会选最小可用索引(如二级索引)做覆盖扫描,完全不访问表数据页 -
COUNT(col)即使col是NOT NULL,MySQL 仍需逐行读取该列值并做IS NOT NULL判断 —— 这意味着必须访问数据行(除非该列本身是索引且能覆盖) - 若
col没索引,COUNT(col)强制全表扫描;COUNT(*)可能仅扫最小索引
所以性能差距可能很大,尤其在大表上:实测 100 万行表中,COUNT(*) 耗时 0.03 秒,COUNT(nullable_col) 却达 0.28 秒。
MyISAM 和 InnoDB 对 count(*) 的处理机制完全不同
这个底层差异直接影响你对“结果一致性”的预期:
- MyISAM 直接从表头缓存读取总行数,
COUNT(*)极快,但COUNT(col)仍要扫描数据(因为需判断NULL) - InnoDB 不缓存行数(事务可见性要求),每次
COUNT(*)都是实时计算;但优化器会智能选索引,而COUNT(col)无法绕过列值检查 - 如果你在 MyISAM 表上看到
COUNT(*)和COUNT(col)结果相同,别归因于“逻辑一致”,很可能只是当前恰好没NULL值而已
真正容易被忽略的点是:InnoDB 下,COUNT(*) 的结果反映的是**当前事务隔离级别下可见的行数**,而 COUNT(col) 在同样条件下还要多一层 NULL 过滤 —— 二者统计口径天然错位,不是 bug,是设计使然。











