COUNT(列名)、COUNT(常量)和COUNT(*)之间的区别
COUNT(列名)、COUNT(常量)和COUNT(*)之间的区别
COUNT(expr) 用于做行数统计,统计的是 expr 不为 NULL 的
行数,那么COUNT( 列名)、 COUNT( 常量) 和 COUNT(*) 这三种语法中,expr 分
别是列名、 常量 和 *。
那么列名、 常量 和 * 这三个条件中,常量 是一个固定值,肯定不为 NULL。*
可以理解为查询整行,所以肯定也不为 NULL,那么就只有列名的查询结果有可能是
NULL 了。
所以, COUNT( 常量 ) 和 COUNT(*) 表示的是直接查询符合条件的数据库表的行
数。而 COUNT( 列名 ) 表示的是查询符合条件的列的值不为 NULL 的行数。
除 了 查 询 得 到 结 果 集 有 区 别 之 外,COUNT(*) 相比COUNT( 常量) 和
COUNT( 列名 ) 来讲,COUNT(*) 是 SQL92 定义的标准统计行数的语法,因为他是
标准语法,所以 MySQL 数据库对他进行过很多优化。
SQL92,是数据库的一个 ANSI/ISO 标准。它定义了一种语言(SQL)以及数
据库的行为(事务、隔离级别等)。
COUNT(*) 的优化
前面提到了COUNT(*) 是SQL92定义的标准统计行数的语法,所以MySQL
数据库对他进行过很多优化。那么,具体都做过哪些事情呢?
这里的介绍要区分不同的执行引擎。MySQL中比较常用的执行引擎就是
InnoDB 和 MyISAM。
MyISAM 和 InnoDB 有很多区别,其中有一个关键的区别和我们接下来要介绍
的 COUNT(*) 有关,那就是 MyISAM 不支持事务,MyISAM 中的锁是表级锁;而
InnoDB 支持事务,并且支持行级锁。
因为MyISAM的锁是表级锁,所以同一张表上面的操作需要串行进行,所以,
MyISAM 做了一个简单的优化,那就是它可以把表的总行数单独记录下来,如果从
一张表中使用 COUNT(*) 进行查询的时候,可以直接返回这个记录下来的数值就可
以了,当然,前提是不能有 where 条件。
MyISAM 之所以可以把表中的总行数记录下来供 COUNT(*) 查询使用,那是因
为 MyISAM 数据库是表级锁,不会有并发的数据库行数修改,所以查询得到的行数
是准确的。
但是,对于 InnoDB 来说,就不能做这种缓存操作了,因为 InnoDB 支持事务,
其中大部分操作都是行级锁,所以可能表的行数可能会被并发修改,那么缓存记录下
来的总行数就不准确了。
但是,InnoDB 还是针对 COUNT(*) 语句做了些优化的。
在InnoDB中,使用COUNT(*)查询行数的时候,不可避免的要进行扫表了,
那么,就可以在扫表过程中下功夫来优化效率了。
从 MySQL 8.0.13 开始,针对 InnoDB 的 SELECT COUNT(*) FROM tbl_
name 语句,确实在扫表的过程中做了一些优化。前提是查询语句中不包含 WHERE
或 GROUP BY 等条件。
我们知道,COUNT(*)的目的只是为了统计总行数,所以,他根本不关心自己
查到的具体值,所以,他如果能够在扫表的过程中,选择一个成本较低的索引进行的
话,那就可以大大节省时间。
我们知道,InnoDB中索引分为聚簇索引(主键索引)和非聚簇索引(非主键索
引),聚簇索引的叶子节点中保存的是整行记录,而非聚簇索引的叶子节点中保存的
是该行记录的主键的值。
所以,相比之下,非聚簇索引要比聚簇索引小很多,所以MySQL 会优先选择
最小的非聚簇索引来扫表。所以,当我们建表的时候,除了主键索引以外,创建一个
非主键索引还是有必要的。
至此,我们介绍完了 MySQL 数据库对于 COUNT(*) 的优化,这些优化的前提
都是查询语句中不包含 WHERE 以及 GROUP BY 条件。