MySQL里的COUNT
count(*)、count(1)、count(主键)、count(字段)的执行效率
在没有where条件的情况下
MyISAM引擎返回结果会比InnoDB快上很多,主要是因为MyISAM会单独记录了表的总行数,而InnoDB没有这么做。
为什么没有这么做呢?主要InnoDB支持了事务的原因,在事务中不同的版本上查询出来的结果是不一样的。例如表中总行数现有10条,事务A启动后未查询,这时启动事务B对表插入一条数据。这时候事务A查询表行数为10条,事务B查询得行数为11条。InnoDB默认使用了可重复读的隔离级别。
mysql中有个show table status的查询,这个查询结果中记录了表行数的字段Rows。查询执行速度很快,但这个结果不可以用,因为这个结果是mysql采样估算得来的,比较不准确。
对表数据为54万的数据进行查询比较,其中a字段未加索引可为空,d字段未加索引不可为空,b字段加了索引不可为空,c字段加了索引可为空。
执行结果耗时:
[SQL]
-- 1
select count(*) from cyj_test ;
受影响的行: 0
时间: 0.086ms
[SQL]
-- 2
select count(1) from cyj_test;
受影响的行: 0
时间: 0.083ms
[SQL]
-- 3
select count(id) from cyj_test;
受影响的行: 0
时间: 0.101ms
[SQL]
-- 4 未加索引可为空
select count(a) from cyj_test;
受影响的行: 0
时间: 0.635ms
[SQL]
-- 5 加了索引不可为空
select count(b) from cyj_test;
受影响的行: 0
时间: 0.101ms
[SQL]
-- 6 加了索引可为空
select count(c) from cyj_test;
受影响的行: 0
时间: 0.129ms
[SQL]
-- 7 未加索引不可为空
select count(d) from cyj_test;
受影响的行: 0
时间: 0.426ms
根据执行时间可得执行效率为:count(*)≈count(1)>count(主键)≈>count(加了索引不可为空字段)>count(加了索引可为空字段)>count(未加了索引不可为空字段)>count(未加了索引可为空字段)
EXPLAIN结果
-- 1
EXPLAIN select count(*) from cyj_test ;
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | cyj_test | index | idex_b | 4 | 544598 | 100 | Using index |
-- 2
EXPLAIN select count(1) from cyj_test;
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | cyj_test | index | idex_b | 4 | 544598 | 100 | Using index |
-- 3
EXPLAIN select count(id) from cyj_test;
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | cyj_test | index | idex_b | 4 | 544598 | 100 | Using index |
-- 4 未加索引可为空
EXPLAIN select count(a) from cyj_test;
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | cyj_test | ALL | 544598 | 100 |
-- 5 加了索引不可为空
EXPLAIN select count(b) from cyj_test;
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | cyj_test | index | idex_b | 4 | 544598 | 100 | Using index |
-- 6 加了索引可为空
EXPLAIN select count(c) from cyj_test;
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | cyj_test | index | idex_c | 123 | 544598 | 100 | Using index |
-- 7 未加索引不可为空
EXPLAIN select count(d) from cyj_test;
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | SIMPLE | cyj_test | ALL | 544598 | 100 |
EXPLAIN结果得知未加索引的会遍历全表扫描得到查询结果,没有走索引,所以4和7查询速度会比其他慢了很多。
count(*)、count(1)、count(id)、count(b)都走了index_b的索引,count(c)走了index_c的索引。这里你可能会有几个问题要问:
1、count(*)、count(1)、count(id)为什么不走主键索引而走了index_b呢?
因为mysql默认使用了InnoDB,索引是B+树的形式。这里主键索引的页子节点存的是数据,而普通索引树存的是主键值,所以主键索引肯定比普通索引树的大很多,优化器会使用找到的那棵最小的树来进行遍历,所以走了
index_b。
2、那为什么走了index_b而不是走了index_c呢?
从
EXPLAIN结果得知,index_b的key_len为4,index_c的key_len为123,key_len表示索引中使用的字节数,所以肯定使用index_b的数据量更小。
从EXPLAIN我们简单得知了没加索引会比加了索引的查询慢了很多,那么都加了索引的情况下会是怎么样的呢?其实是mysql对count()、count(1)、count(id)、count(b)、count(c)的判断各不相同导致的。注:取值和不取值会影响执行速度,因为取值会对数据行进度解析以得到想要的字段。
count(*)
InnoDB遍历整张表,但不取值,count(*)肯定不为空,按行累加就行了。
count(1)
InnoDB遍历整张表,但不取值,server层对于每一行数据返回1,判断1不可能空,按行累加。
count(id)
InnoDB遍历整张表,把每一行的id取出来返回给server层,server层判断不可能为空,按行累加。
count(不可为空字段)
InnoDB遍历整张表,把每一行的这个字段取出来返回给server层,server层判断不可能为空,按行累加。
count(可空字段)
InnoDB遍历整张表,把每一行的这个字段取出来返回给server层,server层判断是不是为空,不为空的按行累加。
count(判断 or null)
假设存在一张子任务表,表主要信息如下:
CREATE TABLE `app_task_child` (
`task_child_id` varchar(40) NOT NULL,
`status` int(11) NOT NULL DEFAULT '1' COMMENT '1.待提交;2.审核中;3.已提交;4.已归档;',
`task_id` varchar(40) DEFAULT NULL COMMENT '母任务',
PRIMARY KEY (`task_child_id`),
KEY `FK6m...` (`task_id`),
CONSTRAINT `FK6m...` FOREIGN KEY (`task_id`) REFERENCES `app_task` (`task_id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
现在有一个需求:统计出各任务下的子任务数、已归档数、审核中数的数据。
SELECT
t.task_id AS taskId,
count(t.task_child_id) AS taskChildNum,
count(t.STATUS = 4) AS ongoingNum,
count(t.STATUS = 2) AS archiveNum
FROM
app_task_child t
GROUP BY
t.task_id

上面的SQL会查询出图一的数据来,这数据一看就知道不对,已归档数和审核中的数量肯定错了。文章上面大概有说到一个意思:count计算的是除了NULL值,其他数据都会加1,例如0或false也都是会加数量1。
t.STATUS = ?判断为false或true,所以count总为加1,导致结果总跟子任务数是一样的。那么就需要想办法当为false时把结果置为NULL。例如有下面两种方法都能得到正确的结果:
-- 方法一
SELECT SQL_NO_CACHE
t.task_id AS taskId,
count(t.task_child_id) AS taskChildNum,
count(IF(t. STATUS = 4, true, NULL)) AS ongoingNum,
count(IF(t. STATUS = 2, true, NULL)) AS archiveNum
FROM
app_task_child t
GROUP BY
t.task_id
-- 方法二
SELECT
t.task_id AS taskId,
count(t.task_child_id) AS taskChildNum,
count(t.STATUS = 4 or NULL) AS ongoingNum,
count(t.STATUS = 2 or NULL) AS archiveNum
FROM
app_task_child t
GROUP BY
t.task_id
方法一的不难理解,这里不进行说明。
方法二(判断 or NULL)可以理解为当判断为0时,会走or后面的表达式,当判断为1时,不走or后面的表达式。判断为1的直接count为1,判断为0时进行NULL的表达式判断,而且0 or NULL为NULL。
在mysql中的or和and判断不像java那样,更像是JavaScript这种弱类型语言的判断,可以把NULL直接进行判断。例如下图中的判断结果

count(判断 or null)性能怎么样?
对面上的表进行加status索引。
ALTER TABLE `app_task_child`
ADD INDEX `index_status` (`status`) USING BTREE ;
执行sql
-- 写法一
EXPLAIN SELECT
t.task_id AS taskId,
count(t.task_child_id) AS taskChildNum,
count(t.STATUS = 2 or null) AS archiveNum
FROM
app_task_child t
GROUP BY
t.task_id;
结果为:
| ... | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|---|---|---|---|---|---|---|---|---|
| ... | index | FK6m... | FK6m... | 123 | 39 | 100 |
执行sql
-- 写法二
EXPLAIN SELECT
t.task_id AS taskId,
count(t.task_child_id) AS taskChildNum,
count(*) AS archiveNum
FROM
app_task_child t
where t.status = 2
GROUP BY
t.task_id;
结果为:
| ... | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
|---|---|---|---|---|---|---|---|---|
| ... | ref | FK6m...,index_status | index_status | 123 | const | 1 | 100 | Using index condition; Using temporary; Using filesort |
就只单单从type字段一个为ref一个为index就可得知写法二性能完爆写法一(可以参考别人的文章)
。那么为什么上面不用写法二呢?实际开发中统计的往往不只统计一个num,可能会统计八九个。所以如果使用写法二,需要写八九个SQL去执行,而写法一只需要一条SQL搞定。还有就是这时写法二花费在数据库连接上的损耗加起来往往是比写法一性能更差些。
如果不在status字段上加索引,EXPLAIN比较出来的结果也是方法二性能稍微好一点,这点大家可以自己试一下
MySQL里的COUNT的更多相关文章
- mysql提示Column count doesn't match value count at row 1错误
mysql提示Column count doesn't match value count at row 1错误,后来发现是由于写的SQL语句里列的数目和后面的值的数目不一致, 比如insert in ...
- 开发中运行mysql脚本,发现提示mysql提示Column count doesn't match value count at row 1错误
开发中运行mysql脚本,发现提示mysql提示Column count doesn't match value count at row 1错误, 调试后发现是由于写的SQL语句里列的数目和后面的值 ...
- MySql 里的IFNULL、NULLIF和ISNULL用法
MySql 里的IFNULL.NULLIF和ISNULL用法 mysql中isnull,ifnull,nullif的用法如下: isnull(expr) 的用法: 如expr 为null,那么isnu ...
- SQLSERVER 里SELECT COUNT(1) 和SELECT COUNT(*)哪个性能好?
SQLSERVER 里SELECT COUNT(1) 和SELECT COUNT(*)哪个性能好? 今天遇到某人在我以前写的一篇文章里问到 如果统计信息没来得及更新的话,那岂不是统计出来的数据时错误的 ...
- MySQL里的wait_timeout
如果你没有修改过MySQL的配置,缺省情况下,wait_timeout的初始值是28800. wait_timeout过大有弊端,其体现就是MySQL里大量的SLEEP进程无法及时释放,拖累系统性能, ...
- MySQL优化之COUNT(*)效率
MySQL优化之COUNT(*)效率 刚给一个朋友解决他写的Discuz!插件的问题,说到MySQL的COUNT(*)的效率,发现越说越说不清楚,干脆写下来,分享给大家. COUNT(*)与COUNT ...
- 用count(*)还是count(列名) || Mysql中的count()与sum()区别
Mysql中的count()与sum()区别 首先创建个表说明问题 CREATE TABLE `result` ( `name` varchar(20) default NULL, `su ...
- mysql中的count(primary_key)、count(1)、count(*)的区别
表结构如下: mysql> show create table user\G; *************************** 1. row ********************** ...
- MySQL里创建外键时错误的解决
--MySQL里创建外键时错误的解决 --------------------------------2014/04/30 在MySQL里创建外键时(Alter table xxx add const ...
随机推荐
- cozmo 入坑日记及开发环境搭建
前几日,朋友在群里发了一个机器人的小视频,视频里机器人可以对话,可以推箱子,开心以后会哈哈大笑,非常有趣. 详细了解里一下,这是个叫 cozmo 的智能机器人,可以配合 SDK 用 python 编程 ...
- 洛谷P3877 [TJOI2010]打扫房间 解题报告
首先整理一下条件: 1.恰好进出每个需打扫的房间各一次 2.进出每个房间不能通过同一个门 (其实前两个条件是一回事) 3.要求每条路线都是一个闭合的环线 4.每条路线经过的房间数大于2 让你在一个n* ...
- 从7点到9点写的小程序(用了模块导入,python终端颜色显示,用了点局部和全局可变和不可变作用域,模块全是自定义)
未完待续的小程序 要是能做的好看为啥不做的好看 在同目录下生成程序 1.程序文件 run.py from login import login from register import registe ...
- Android/AndroidStudio/idea使用教程之git使用(详细)(码云)
已经安装好了AndroidStudio,安装教程 本教程是作者自己摸索出来的,有不足之处还请大家海涵.多多拍砖,互相学习. 第一步:下载git,安装git客户端 直接百度git,下载git 安装g ...
- 哥们,B/S了解吗?——啥玩意,我是敲代码的
了解B/S和C/S 前言:......“学好长时间编程了,JavaSE学完了,前端也简单学了”.....“那你学这么多,讲讲B/S吧”......“B/S?这是个啥玩意?没听过”......“靠,牛逼 ...
- 第一篇:开始进入 django 之旅
文中所有示例代码的仓库地址:https://github.com/HelloGitHub-Team/HelloDjango-blog-tutorial 开发环境说明 本教程写作时开发环境的系统平台为 ...
- Servlet和JSP知识总结
1.Servlet接口有哪些方法及Servlet生命周期 Servlet接口定义了5个方法,前三个方法与Servlet生命周期有关: void init() void service() void d ...
- IT技术人员的自我修养
1. 前言 在IT领域摸爬滚打多年,从一个普通程序员到技术主管,到技术经理,再到技术总监,踩过不少坑.加过不少班,也背过不少锅,在提升自身技术能力与管理能力的同时,也一直在思考,作为IT ...
- git pull 出现non-fast-forward的错误
1.git pull origin daily_liu_0909:liu_0909 出现non-fast-forward的错误,证明您的本地库跟远程库的提交记录不一致,即 你的本地库版本需要更新2.g ...
- 用python twilio模块实现发手机短信的功能
前排提示:这个模块不是用于对陌生人进行短信轰炸和电话骚扰的,这个模块也没有这个功能,如果是抱着这个心态来的,可以关闭网页了 语言:python 步骤一:安装twilio模块 pip install t ...