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 ...
随机推荐
- Netty使用Google Protocol Buffer完成服务器高性能数据传输
一.什么是Google Protocol Buffer(protobuf官方网站) 下面是官网给的解释: Protocol buffers are a language-neutral, platfo ...
- 2015.11.27---Java
public class star{ public static void main(String[] args) { System.out.print("ha"); } }
- python中的内存机制
首先要明白对象和引用的概念 (例子:a=1, a为引用,1为对象,对象1的引用计数器为1,b=1此时内存中只有一个对象1,a,b都为引用,对象的引用计数器此时为2,因为有两个引用) a=1,b=1 i ...
- windos10专业版激活(可用)
电脑提示Windows许可证即将到期,于是自己就在网上找了一些教程,但是并没有激活成功,反而由即将到期变为了通知状态,尝试了各种密钥都不行,也下载了激活工具如暴风激活工具,KMS都不管用,尝试了好多方 ...
- idea新建javaweb工程
最近尝试了idea的使用,将idea建立javaweb工程的步骤记录下来 1.方框里边是重点 2.next后输入工程文件名点击finish 3.如图看到项目文件夹里边没有WEB-INF文件夹及里边的w ...
- Oracle 统计信息介绍
统计信息自动执行需要以下条件满足: dba_autotask_task 字段status值ENABLED dba_autotask_client 字段status值ENABLED dba_auto ...
- 深入理解JVM-类加载器深入解析(2)
深入理解JVM-类加载器深入解析(2) 加载:就是把二进制形式的java类型读入java虚拟机中 连接: 验证: 准备:为类变量分配内存,设置默认值.但是在到达初始化之前,类变量都没有初始化为真正的初 ...
- HTML第六章 盒子模型
什么是盒子模型: (1)边框: (2)内边距: (3)外边距: (4)元素内容·: (5)背景色·: 边框: 属性: 颜色(border-color),粗细(border-width),样式(bord ...
- Unity经典游戏教程之:合金弹头
版权声明: 本文原创发布于博客园"优梦创客"的博客空间(网址:http://www.cnblogs.com/raymondking123/)以及微信公众号"优梦创客&qu ...
- 分布式ID系列(4)——Redis集群实现的分布式ID适合做分布式ID吗
首先是项目地址: https://github.com/maqiankun/distributed-id-redis-generator 关于Redis集群生成分布式ID,这里要先了解redis使用l ...