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 ...
随机推荐
- [leetcode] 106. Construct Binary Tree from Inorder and Postorder Traversal(medium)
原题地址 思路: 和leetcode105题差不多,这道题是给中序和后序,求出二叉树. 解法一: 思路和105题差不多,只是pos是从后往前遍历,生成树顺序也是先右后左. class Solution ...
- Apache Httpd 启用重定向 rewrite
1.启用模块 由:#LoadModule rewrite_module modules/mod_rewrite.so 更改为:LoadModule rewrite_module modules/mod ...
- python课堂整理19----迭代器和生成器
一.概念 • 迭代器协议: 对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么引起一个stopIteration异常,以终止迭代(只能往后走,不能往前退) • 协议是一种约定,pyt ...
- SpringMVC表单对象绑定到@ModelAttribute
支持绑定表单对象 jsp: <%@ taglib prefix="form" uri="http://www.springframework.org/tags/fo ...
- python常用函数进阶(2)之map,filter,reduce,zip
Basic Python : Map, Filter, Reduce, Zip 1-Map() 1.1 Syntax # fun : a function applying to the iterab ...
- TestNG使用@Parameter给要测试的方法传递参数
当需要测试的方法含有参数时,可以通过@Parameters 注解给该方法传递参数. 比如下面这个类,要调用whoami则必须写一个main函数,然后在main函数中调用该函数,并传入参数,使用Test ...
- Docker 安装部署Sql Server
前言 在如今,容器化概念越来越盛行,.Net Core项目也可以跨平台部署了,那么思考下Sql Server能不能呢?当然是可以的啦.本文今天就是介绍Docker部署配置和连接Sql Server.本 ...
- 使用钉钉对接禅道的bug系统,实现禅道提的bug实时在钉钉提醒并艾特对应的开发人员处理
现在公司测试中有一个痛点是每次测试人员提完bug后,需要定期去提醒开发人员查看禅道的bug记录及修复bug. 导致测试人员在项目测试中不仅要测试整个软件,还要负起实时监督提醒功能的“保姆角色”,身心疲 ...
- Spring条件注解@Conditional
@Conditional是Spring4新提供的注解,它的作用是根据某个条件创建特定的Bean,通过实现Condition接口,并重写matches接口来构造判断条件.总的来说,就是根据特定条件来控制 ...
- Android 9.0 关机流程分析
极力推荐文章:欢迎收藏 Android 干货分享 阅读五分钟,每日十点,和您一起终身学习,这里是程序员Android 本篇文章主要介绍 Android 开发中的部分知识点,通过阅读本篇文章,您将收获以 ...