Skip to content

MySQL 中 count(*)、count(1) 和 count(字段名) 有什么区别? ​

  • 标签:后端、MySQL、数据库

🏛 业务架构师 ​

先讲语义:count(*) 与 count(1) 统计总行数(含 NULL),InnoDB 对 count(*) 做了专门优化,不取值只数行,性能基本等价;count(字段) 统计该列非 NULL 的行数——语义就不同,NULL 行不计入。

业务视角的重点是:大表实时 count 本身就是伪需求。千万行的表执行 count(*),InnoDB 因为 MVCC 要数"对自己可见的行",无法只读一个元数据数字,全表扫下来几秒到几十秒,运营每刷一次报表,数据库就被拖一次。

我的治理方案按精度需求分档:

  • 精确计数:用计数表/Redis 计数器维护,在业务写入路径上同步增减(配合补偿任务纠偏)——订单总量、库存余量这类交易数字必须精确;
  • 近实时:binlog 同步到 ES/ClickHouse,报表侧聚合查询,交易库与报表库物理隔离,运营随便查都不影响下单;
  • 允许误差:explain 的估算行数、information_schema.tables 的 table_rows、或 HyperLogLog——UV 这类指标天然可接受 1% 内误差。

一句话:count 的区别是表层知识,"把统计流量和交易流量分开"才是架构决策。让运营的报表需求拖着交易库跑,是我评审时一定要拦的反模式。

🏗 系统架构师 ​

先给结论:InnoDB 下三者性能基本等价,count(*) 是语义清晰的首选。差异在语义和优化细节:

  • count(*):统计行数,SQL 标准语义"该表行数",InnoDB 专门优化(不取值),MySQL 5.7 起自动选最小的索引树来扫;
  • count(1):对每行计算常量 1 计数,效果与 count(*) 相同,不存在谁更快(网上说 count(1) 更快是老皇历);
  • count(字段):要取出字段值并跳过 NULL,语义是"该字段非 NULL 的行数";如果字段定义 NOT NULL,优化器可以按覆盖索引用,但多了取值判断,通常最慢;字段无索引时甚至要回表。

为什么 InnoDB 的 count 慢:MVCC 下不同事务的可见行不同,无法像 MyISAM 那样存一个总数,只能扫一棵索引树逐行判断。

大表计数的架构解法:精确值走计数表(业务里维护,事务内更新)、Redis 计数 + 定时校准;容忍近似用 explain 的 rows 或 information_schema.tables 的 TABLE_ROWS;分页总数用"下一页有无"替代精确 total。这是我做列表页的标准设计。

💻 全栈开发专家 ​

语义区别:count(*) 和 count(1) 统计所有行;count(字段) 统计该字段非 NULL 的行数。

性能层面(InnoDB):

  • count(*) ≈ count(1):优化器会自动挑一棵最小的索引树遍历计数,不必碰聚簇索引,二者几乎无差别(别被"count(1) 更快"的老谣言误导)。
  • count(字段):需要判断字段值是否为 NULL,如果该字段不在被选中的索引里还会回表,最慢。
  • MyISAM 的 count(*) 有总行数元数据所以飞快,InnoDB 因为 MVCC(不同事务看到的行数不同)无法缓存总行数,每次都是实扫一棵索引树。

大表计数的工程方案(这是我会主动展开的):

  • 精确值不敏感:EXPLAIN 的 rows 估算,误差可接受就用它;
  • 需要较准:计数表(业务变更时同事务 ±1)或 Redis 计数(注意与 DB 的最终一致);
  • 真要精确 count:走最小二级索引并接受扫树耗时,或离线统计表定期刷新;
  • 分页 count 优化:深翻页场景干脆不返回总数,或"有下一页"标记代替。