MySQL里的COUNT

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: MySQL里的COUNTcount(*)、count(1)、count(主键)、count(字段)的执行效率在没有where条件的情况下MyISAM引擎返回结果会比InnoDB快上很多,主要是因为MyISAM会单独记录了表的总行数,而InnoDB没有这么做。

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比较出来的结果也是方法二性能稍微好一点,这点大家可以自己试一下
原文地址https://www.cnblogs.com/cnJun/p/11404567.html

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
9月前
|
关系型数据库 MySQL
mysql统计数据表中同一字段不同状态的COUNT()语句
mysql统计数据表中同一字段不同状态的COUNT()语句
67 0
|
5月前
|
SQL 关系型数据库 MySQL
解决MySQL count统计数目错误的问题
解决MySQL count统计数目错误的问题
44 0
|
6月前
|
SQL 算法 关系型数据库
【MySQL系列】统计函数(count,sum,avg)详解
文章目录 🌈一、COUNT函数 创建一个表T1 1.COUNT函数的定义: 2.COUNT函数的使用方式: 1️⃣count(*) (1)count(*)定义: (2)具体使用: (3)
|
6月前
|
NoSQL 关系型数据库 MySQL
MySQL学习笔记-不同count统计的比较
MySQL学习笔记-不同count统计的比较
42 0
|
8月前
|
SQL 存储 关系型数据库
为什么mysql的count()方法这么慢?
为什么mysql的count()方法这么慢?
68 0
|
8月前
|
关系型数据库 MySQL 数据挖掘
数据统计与计数:解析MySQL中的COUNT()函数
在数据库管理中,统计某一列数据的行数是常见的数据分析任务,COUNT()函数正是用于帮助我们实现这一目标的工具。
142 0
|
9月前
|
关系型数据库 MySQL 索引
【MySQL】count(1)、count(*)、count(字段)、count(主键)的区别
【MySQL】count(1)、count(*)、count(字段)、count(主键)的区别
162 0
|
10月前
|
SQL 存储 关系型数据库
MySQL中count(*)和information_schema.tables中的table_rows值不相同
MySQL中count(*)和information_schema.tables中的table_rows值不相同
264 0
|
10月前
|
存储 关系型数据库 MySQL
MySQL中count是怎样执行的?———count(1),count(id),count(非索引列),count(二级索引列)的分析
MySQL中count是怎样执行的?———count(1),count(id),count(非索引列),count(二级索引列)的分析
110 0
MySQL中count是怎样执行的?———count(1),count(id),count(非索引列),count(二级索引列)的分析
|
11月前
|
存储 SQL 安全
Mysql报错注入原理分析(count()、rand()、group by)
Mysql报错注入原理分析(count()、rand()、group by)
161 0