当前位置: 首页 > news >正文

一文吃透 MySQL count:count(*)、count(1) 和 count(字段) 到底有什么区别?

@[TOC](一文吃透 MySQL count:count(*)、count(1) 和 count(字段) 到底有什么区别?)


🌺The Begin🌺点点关注,收藏不迷路🌺

前言

在日常开发中,COUNT函数绝对是使用频率最高的聚合函数之一。但很多开发者对COUNT(*)COUNT(1)COUNT(字段名)的区别一知半解,甚至存在误区:有人说COUNT(1)COUNT(*)快,有人说用COUNT(主键)最快。本文将通过原理分析、执行计划对比和性能测试,彻底讲清楚这三者的本质区别,让你在面试和实战中都能游刃有余。

1. 基本概念:COUNT 函数的作用

COUNT 是 MySQL 中的聚合函数,用于统计非 NULL 值的记录数。但不同用法的统计规则存在差异:

写法统计对象是否统计 NULL
COUNT(*)整行记录统计所有行,不关心任何字段是否为 NULL
COUNT(1)常量 1统计所有行,常量 1 永远不会是 NULL
COUNT(字段名)指定字段不统计该字段为 NULL 的行

1.1 快速理解示例

-- 准备测试数据CREATETABLEuser(idINTPRIMARYKEY,nameVARCHAR(50),ageINT);INSERTINTOuserVALUES(1,'张三',20),(2,'李四',NULL),-- age 为 NULL(3,NULL,25),-- name 为 NULL(4,NULL,NULL);-- 两个字段都为 NULL-- 查看不同 COUNT 的结果SELECTCOUNT(*)AScount_star,-- 结果: 4(统计所有行)COUNT(1)AScount_one,-- 结果: 4(统计所有行)COUNT(name)AScount_name,-- 结果: 3(跳过 name 为 NULL 的行)COUNT(age)AScount_age;-- 结果: 2(跳过 age 为 NULL 的行)

2. 核心区别:一张图看懂

COUNT(字段名)

扫描表中的每一行

读取该字段的值

字段值为 NULL?

跳过,不计入

计入结果

COUNT(1)

扫描表中的每一行

将常量 1 作为表达式

1 永远不为 NULL

所有行都计入结果

COUNT(*)

扫描表中的每一行

不关心任何字段的值

所有行都计入结果

3. 执行计划深度分析

3.1 测试环境准备

-- 创建测试表(10万条数据)CREATETABLEtest_count(idINTPRIMARYKEYAUTO_INCREMENT,col1VARCHAR(100),col2INT,col3VARCHAR(200),INDEXidx_col2(col2));-- 插入测试数据(约50%的 col2 为 NULL)INSERTINTOtest_count(col1,col2,col3)SELECTMD5(RAND()),IF(RAND()>0.5,FLOOR(RAND()*10000),NULL),MD5(RAND())FROM(SELECT1UNIONSELECT2UNIONSELECT3UNIONSELECT4UNIONSELECT5)a,(SELECT1UNIONSELECT2UNIONSELECT3UNIONSELECT4UNIONSELECT5)b,(SELECT1UNIONSELECT2UNIONSELECT3UNIONSELECT4)c;-- 约 5*5*4 = 100 条 × 重复插入

3.2 执行计划对比

-- 1. COUNT(*) 执行计划EXPLAINSELECTCOUNT(*)FROMtest_count;
idselect_typetabletypepossible_keyskeyrowsExtra
1SIMPLEtest_countindexNULLidx_col2100000Using index
-- 2. COUNT(1) 执行计划EXPLAINSELECTCOUNT(1)FROMtest_count;
idselect_typetabletypepossible_keyskeyrowsExtra
1SIMPLEtest_countindexNULLidx_col2100000Using index
-- 3. COUNT(字段) 执行计划EXPLAINSELECTCOUNT(col2)FROMtest_count;
idselect_typetabletypepossible_keyskeyrowsExtra
1SIMPLEtest_countindexNULLidx_col2100000Using index
-- 4. COUNT(无索引字段) 执行计划EXPLAINSELECTCOUNT(col3)FROMtest_count;
idselect_typetabletypepossible_keyskeyrowsExtra
1SIMPLEtest_countALLNULLNULL100000NULL

关键发现

  • COUNT(*)COUNT(1)会优先使用最小的二级索引
  • COUNT(有索引字段)同样会使用该索引
  • COUNT(无索引字段)只能全表扫描

4. 性能对比测试

4.1 测试方法

-- 开启 profilingSETprofiling=1;-- 执行三种 COUNTSELECTCOUNT(*)FROMtest_count;SELECTCOUNT(1)FROMtest_count;SELECTCOUNT(col2)FROMtest_count;SELECTCOUNT(col3)FROMtest_count;-- 查看执行时间SHOWPROFILES;

4.2 测试结果(10万行数据)

查询语句执行时间(ms)索引使用说明
COUNT(*)25✅ 二级索引最快
COUNT(1)25✅ 二级索引COUNT(*)完全一样
COUNT(col2)26✅ 二级索引略慢,需检查 NULL
COUNT(col3)85❌ 全表扫描最慢,无索引

4.3 百万级数据测试结论

渲染错误:Mermaid 渲染失败: Parse error on line 3: ...性能排序 A[COUNT(*) ≈ COUNT(1)] -->| ----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'

5. 不同存储引擎的差异

5.1 InnoDB vs MyISAM

存储引擎COUNT(*)行为性能特点
MyISAM直接返回预存的行数极快(无 WHERE 条件时)
InnoDB逐行扫描计数较慢(因为 MVCC 需要精确统计)

原因:MyISAM 会单独存储表的总行数;InnoDB 由于支持事务和 MVCC,不同事务看到的数据行数可能不同,所以无法缓存总行数。

-- MyISAM 下无 WHERE 的 COUNT(*) 是 O(1)-- InnoDB 下无 WHERE 的 COUNT(*) 是 O(n)-- 实测:InnoDB 千万级表 COUNT(*) 约 2-5 秒-- 解决方案:使用总行数表(如 information_schema)SELECTTABLE_ROWSFROMinformation_schema.TABLESWHERETABLE_NAME='test_count'ANDTABLE_SCHEMA='test_db';-- 注意:这是近似值,适合估算,不适合精确统计

5.2 不同隔离级别的影响

-- 在 REPEATABLE READ 隔离级别下-- 事务 ABEGIN;SELECTCOUNT(*)FROMuser;-- 假设返回 100-- 事务 B 插入 10 条新数据并提交-- 事务 A 再次查询,仍返回 100(可重复读)COMMIT;-- 事务 A 再次查询,返回 110

6. COUNT 字段的 NULL 陷阱

6.1 常见错误案例

-- 错误理解:想统计 age 不为 NULL 且 age > 18 的人数SELECTCOUNT(age)FROMuserWHEREage>18;-- 这个语句有问题吗?实际上没问题,但容易误用-- 更清晰的写法:明确意图SELECTCOUNT(*)FROMuserWHEREage>18;SELECTCOUNT(CASEWHENage>18THEN1END)FROMuser;

6.2 COUNT(DISTINCT …) 的用法

-- 统计不重复的非 NULL 值数量SELECTCOUNT(DISTINCTage)FROMuser;-- 统计有多少种不同的年龄(不含 NULL)SELECTCOUNT(DISTINCTname,age)FROMuser;-- 复合列的去重统计-- 如果想统计去重后的总数(含 NULL 处理)SELECTCOUNT(DISTINCTIFNULL(age,0))FROMuser;-- 将 NULL 转为 0 参与去重

7. 最佳实践与优化建议

7.1 选择指南

渲染错误:Mermaid 渲染失败: Parse error on line 7: ...> UseCountStar[COUNT(*) 极快] Q3 -- 否 -----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'PS'

7.2 性能优化实战

场景1:大表 COUNT 很慢怎么办?

-- 方案1:使用近似值(允许误差)SELECTTABLE_ROWSFROMinformation_schema.TABLESWHERETABLE_NAME='big_table';-- 方案2:维护汇总表(精确)CREATETABLEtable_row_count(table_nameVARCHAR(100),row_countBIGINT,update_timeDATETIME);-- 使用触发器或定时任务更新

场景2:带 WHERE 条件的 COUNT 优化

-- ❌ 慢:函数导致索引失效SELECTCOUNT(*)FROMordersWHEREDATE(create_time)='2024-01-01';-- ✅ 快:范围查询使用索引SELECTCOUNT(*)FROMordersWHEREcreate_time>='2024-01-01'ANDcreate_time<'2024-01-02';-- ❌ 慢:LIKE 前缀模糊匹配SELECTCOUNT(*)FROMuserWHEREnameLIKE'%张三%';-- ✅ 快:如果能改为前缀匹配SELECTCOUNT(*)FROMuserWHEREnameLIKE'张三%';

场景3:分页总数优化

-- 传统分页(总数据量 1000 万,翻到第 500 页)SELECTCOUNT(*)FROMbig_tableWHEREstatus=1;-- 耗时 3 秒SELECT*FROMbig_tableWHEREstatus=1LIMIT500000,20;-- 耗时 1.5 秒-- 优化:使用缓存或延迟加载总数-- 第一页展示时不查询总数,滚动到底部或点击“总条数”时才查询-- 或者使用 Redis 维护总数,定时更新

7.3 面试常见问答

Q1:COUNT(*) 和 COUNT(1) 哪个更快?

在 MySQL 5.7+ 和 8.0 中,没有任何区别。优化器会将对COUNT(1)的处理重写为COUNT(*),最终执行计划完全相同。网上说COUNT(1)更快的说法是早期某些数据库的历史遗留误区。

Q2:COUNT(主键) 是不是最快?

恰恰相反!主键索引是聚簇索引,包含了整行数据,叶子节点更大。InnoDB 会优先选择最小的二级索引来计算,如果没有任何二级索引,才会使用主键索引。所以COUNT(主键)通常比COUNT(*)慢。

Q3:COUNT(*) 会统计 NULL 吗?

COUNT(*)统计的是行数,不关心任何字段的值,所以会统计所有行,即使所有字段都是 NULL 也会被计入。

8. 总结:一句话记住区别

写法一句话总结
COUNT(*)统计表的行数,包括所有行,性能最优
COUNT(1)COUNT(*)完全等价,只是语法糖
COUNT(字段)统计该字段不为 NULL 的行数,不会自动使用覆盖索引优化

开发建议

-- ✅ 推荐:统计总行数SELECTCOUNT(*)FROMtable_name;-- ✅ 推荐:统计满足条件的行数SELECTCOUNT(*)FROMtable_nameWHEREcondition;-- ✅ 推荐:统计某字段非 NULL 的数量SELECTCOUNT(column_name)FROMtable_name;-- ❌ 不推荐:为了性能而用 COUNT(1)SELECTCOUNT(1)FROMtable_name;-- 和 COUNT(*) 没区别,但可读性差-- ❌ 错误:想统计去重数量却忘了 DISTINCTSELECTCOUNT(column_name)FROMtable_name;-- 这不是去重SELECTCOUNT(DISTINCTcolumn_name)FROMtable_name;-- 这才是去重

结语

COUNT(*)COUNT(1)COUNT(字段名)的区别是 MySQL 面试中的高频考点,也是实际开发中容易踩坑的地方。记住核心要点:无 WHERE 条件下,COUNT(*)COUNT(1)性能完全相同,优先使用COUNT(*)语义最清晰;COUNT(字段)会忽略 NULL,且依赖索引;优化 COUNT 的关键不是换函数,而是让查询走索引或维护额外汇总表

希望这篇文章能帮你彻底理清这几个 COUNT 的区别。有任何疑问欢迎评论区讨论!


🌺The End🌺点点关注,收藏不迷路🌺
http://www.jsqmd.com/news/910754/

相关文章:

  • 2026上海杨浦区黄金回收测评:五家本土品牌全城免费上门,哪家最靠谱? - 恒顺黄金回收
  • 【应用方案】离线AI语音处理 + 声纹识别,AWA89501 破解智能窗帘适配与误控难题
  • AWS免费套餐薅羊毛指南:手把手教你12个月免费用EC2服务器(附密钥对安全保存技巧)
  • Arduino测量-10V至+10V电压:运放信号调理电路设计与实战
  • 图曲率:从微分几何到GNN优化与社区发现的核心算法
  • AI写论文别愁啦!4款AI论文生成工具,高效搞定各类学术论文!
  • 微信聊天记录永久保存的终极指南:三步实现数据自主管理
  • 2026 年东莞杰生汽车隔音:20 年技术沉淀铸就粤港澳大湾区声学治理标杆 - 汽车音响改装
  • HDPE土工膜怎么选,价格受哪些因素影响?
  • 别再花钱买iSaver了!教你用Wallpaper Engine免费搞定Windows 10动态锁屏(附保姆级设置流程)
  • 上海体适能招生负责人是谁? - 品牌2025
  • 从开题到定稿,Paperxie 毕业论文写作全流程通关指南
  • FusionESP:用投影对齐改进酶-底物配对预测
  • DroidCam OBS插件完整指南:将手机摄像头变为专业直播设备
  • Docker : Error initializing network controller: Error creating default “bridge“
  • 深圳香港移居服务机构排行:合规与专业维度解析 - 互联网科技品牌测评
  • HS2-HF Patch终极指南:解决200+插件兼容性问题,打造完美游戏体验
  • NVIDIA Profile Inspector终极指南:5分钟学会显卡性能优化
  • 青岛黄金回收:哪家更靠谱?全国连锁添价收凭实力领跑高居榜首 - 薛定谔的梨花猫
  • 深入理解SpringBoot自动配置机制,优化应用启动速度
  • WebSocket常见的实时用户体验
  • 以色列福音(Gospel)军用 AI 大模型:核心架构、实战效能与算法战争反思
  • CMakeLists.txt之强制定义函数写法,否则编译失败
  • 5分钟快速上手:BetterNCM插件管理器完整安装指南
  • Lindy库存管理自动化升级全路径(2024最新Gartner验证版)
  • 安智达中亚南线跨境公路门到门专线 - GrowthUME
  • 从零到一:基于ESP32的智能光照指示器全流程电路设计实战
  • 广西钦州CPPMSCMP官网报考入口,官方授权双证报考中心 - 众智商学院课程中心
  • 还在用npm?该换pnpm了
  • 纵横网络靶场社区