count(*)统计所有行,count(列名)仅统计该列非null的行;二者语义根本不同,前者计物理行数,后者计非null值数,选错将导致结果错误。

COUNT(*) 统计所有行,COUNT(列名) 只统计该列非 NULL 的行——语义不同,不是性能优劣问题,选错就查错数。
count(*) 和 count(列名) 返回结果可能完全不同
这是最常被忽略的根本差异:两者根本不是“快慢”之争,而是“算什么”的区别。
-
COUNT(*)数的是物理行数,哪怕整行全是NULL也算 1 行 -
COUNT(列名)会逐行检查该列值,NULL就跳过,不计入结果 - 例如表有 5 行,其中
email列有 2 个NULL,那么COUNT(*)返回 5,COUNT(email)返回 3 - 空字符串
''、数字0、布尔false都算非NULL,只有 SQL 的NULL被忽略
count(*) 在多数引擎下能走最窄索引,count(列名) 依赖该列是否可为空
执行效率不能脱离具体场景谈,关键看有没有可用索引和约束。
-
COUNT(*)在 InnoDB 中会自动选择占用空间最小的非空索引(比如INT主键比DATETIME索引更优),避免全表扫描 -
COUNT(列名)如果该列上有NOT NULL约束 + 索引,优化器可能用同一索引,性能接近COUNT(*) - 但如果该列允许
NULL,数据库必须读取实际值判断是否为NULL,无法纯靠索引元数据,IO 开销上升 - MyISAM 是特例:
COUNT(*)直接返回元数据缓存值,极快;COUNT(列名)仍需扫描
count(1) 和 count(*) 在语义与性能上基本等价
别被“用 COUNT(1) 更快”的旧经验误导,现代优化器已无实质差别。
-
COUNT(1)被解析为“对常量表达式求值”,因1永不为NULL,效果 ≡COUNT(*) - MySQL、PostgreSQL、SQL Server 等主流引擎对二者生成相同执行计划,实测耗时差异在毫秒级且不可复现
-
COUNT(*)是 SQL 标准写法,语义清晰,推荐优先使用;COUNT(1)属于历史惯性写法,无必要替换 - 注意:
COUNT(0)同样有效,但易引发误解,不建议用
JOIN 场景下 count(列名) 容易误算主表基数
一对多关联时,直接用 COUNT(列名) 统计主表记录数,几乎必然出错。
- 例如
LEFT JOIN orders ON users.id = orders.user_id后,COUNT(users.id)会把一个用户多个订单重复计数 - 想统计“有多少用户下过单”,必须用
COUNT(DISTINCT users.id),而非COUNT(users.id) - 如果
users.id允许NULL,COUNT(users.id)还会漏掉这些用户(因NULL不计入) - 真正安全的主表行数统计,仍是
COUNT(*)(针对主表别名,如COUNT(DISTINCT u.id)或子查询)
真正容易踩坑的不是语法,而是没想清楚“我要数什么”:是行存在性,还是某列的有效值?前者用 COUNT(*),后者才用 COUNT(列名)。索引、引擎、NULL 处理都是后续优化点,前提得先算对。










