根本原因是三者语义定义不同:COUNT(*)统计所有物理行(含NULL),COUNT(列名)仅统计该列非NULL行,COUNT(DISTINCT 列名)先剔除NULL再对剩余值去重计数。

COUNT(*)、COUNT(列名)、COUNT(DISTINCT 列名)三者统计结果为什么不同
根本原因在于它们处理 NULL 和重复值的逻辑完全不同,不是写法差异,而是语义定义不同。
-
COUNT(*)统计结果集里的所有物理行,不管任何列是否为NULL,也不管值是否重复 -
COUNT(列名)只统计该列值不为NULL的行——''、0、false都算非NULL,只有真正的NULL被跳过 -
COUNT(DISTINCT 列名)先过滤掉NULL,再对剩余值去重,最后计数;不同数据库对NULL是否参与去重有分歧(多数视作一个独立值,但不计入最终计数)
COUNT(DISTINCT) 在 JOIN 场景下容易误用
一对多关联时,COUNT(DISTINCT) 是唯一能正确还原主表基数的手段,否则会因膨胀行数导致高估。
- 比如
LEFT JOIN orders ON users.id = orders.user_id后,直接COUNT(*)得到的是订单总行数,不是用户数 - 想统计“有多少用户下过单”,必须写
COUNT(DISTINCT users.id),而不是COUNT(users.id) - 如果
users.id本身允许NULL,那COUNT(DISTINCT users.id)会忽略这些NULL,结果才是有效用户数
COUNT(DISTINCT) 性能差不是错觉,是必然开销
它必须在内存或临时磁盘中维护哈希表或排序结构来去重,数据量一上去就明显拖慢。
- InnoDB 对
COUNT(*)有索引优化(尤其主键索引),但COUNT(DISTINCT)几乎无法绕过全字段扫描 - 千万级表上,
COUNT(DISTINCT)可能比COUNT(*)慢 5–10 倍,且内存占用陡增 - 线上大表慎用;替代方案包括:预计算存入汇总表、用
APPROX_COUNT_DISTINCT()(MySQL 8.0+/PostgreSQL)、或采样估算
常见错误:把 COUNT(DISTINCT) 当成 SELECT DISTINCT 的等价写法
两者目的不同:SELECT DISTINCT 返回去重后的完整行,COUNT(DISTINCT) 只返回一个数字——别指望它能帮你查出具体有哪些唯一值。
- 想看“都有哪些部门”,写
SELECT DISTINCT department FROM employees - 想看“一共几个部门”,才用
COUNT(DISTINCT department) - 试图写
SELECT name, COUNT(DISTINCT department)会报错(没加GROUP BY),因为聚合和非聚合字段不能混用
NULL 的双重影响:它既不参与 COUNT(列名) 计数,又在 COUNT(DISTINCT 列名) 中被排除——这两步不是叠加,而是先后发生。

















