MySQL count(*)、count(1) 和count(字段)的区别以及count()查询优化手段

178 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第17天,点击查看活动详情

MySQL的count(*)、count(1) 和count(字段)的区别以及count()查询优化手段。

1 几种count查询的区别

count()是一个聚合函数,对于返回的结果集,一行行地判断,如果count函数的参数不是NULL,累计值就加1,否则不加。最后返回累计值。

count(*)、count(主键id)和count(1) 都表示返回满足条件的结果集的总行数;而count(字段),则表示返回满足条件的数据行里面,参数“字段”不为NULL的总个数。

对于count(主键id)来说,InnoDB引擎会遍历整张表,把每一行的id值都取出来,返回给server层。server层拿到id后,判断id是不可能为空的,就按行累加。

对于count(1)来说,InnoDB引擎遍历整张表,但不取值。server层对于返回的每一行,放一个数字“1”进去,判断是不可能为空的,按行累加。

总的来说,count(1)执行得要比count(主键id)快一些。因为从引擎返回id会涉及到解析数据行,以及拷贝字段值的操作。

对于**count(字段)**来说:

  1. 如果这个“字段”是定义为not null的话,一行行地从记录里面读出这个字段,判断不可能为null,按行累加;
  2. 如果这个“字段”定义允许为null,那么执行的时候,判断到有可能是null,还要把值取出来再判断一下,不是null才累加。

count(*)是例外,优化器专门对其做了优化,并不会把全部字段取出来,而是直接按行累加。

所以结论是:按照效率排序的话,count(字段)<count(主键id)<count(1)≈count(*),所以尽量使用count(*)就行了。

2 优化COUNT()查询

如果COUNT()的括号中定义了列名或其它表达式,COUNT就会统计这个表达式有值的次数,不会统计Null。

COUNT的另一种形式就是统计结果中行的数量,当MySql知道括号中的表达式永远都不会为NULL的时候,它就会按这种方式工作。例如COUNT(*),它是COUNT的一种特例,它不会把通配符*展开成所有的列,而是忽略所有的列并统计行数。

一个最常见的错误就是,在括号内指定了一个列却希望统计结果集的行数。如果希望知道的是结果集的行数,最好使用COUNT(*),这样写意义清晰,性能也会很好。

对于MyISAM存储引擎,当在单表中没有限定where查询条件时COUNT(*)是非常快的,因为MyISAM本身已经在磁盘上存了这个行数总值,但是当存在where限定条件,也是需要进行查询统计的。

为什么InnoDB不跟MyISAM一样,也把数字存起来呢? 这是因为即使是在同一个时刻的多个查询,由于多版本并发控制(MVCC)的原因,InnoDB表“应该返回多少行”对于不同的查询也是不确定的。InnoDB引擎执行count(*)的时候,需要把数据一行一行地从引擎里面读出来,每一行记录都要判断自己是否对这个会话可见,然后累积计数。因此对于count(*)请求来说,InnoDB只好把数据一行一行地读出依次判断,可见的行才能够用于计算“基于这个查询”的表的总行数。

count优化:

  1. 简单的优化:如果只需要近似值,那么使用EXPLAIN出来的优化器估算的行数rows就是一个不错的近似值,执行EXPLAIN并不需要真正地去执行查询,所以成本很低。
  2. 更复杂的优化:通常来说,COUNT()都需要扫描大量的行(意味着要访问大量数据)才能获得精确的结果,因此是很难优化的。MySQL层面能做的通常只有是覆盖索引了,尽量通过索引来统计数量,或者单独使用一个汇总表来统计,或者增加一个外部的缓存系统用来计数。

参考资料:

  1. 《 MySQL 技术内幕: InnoDB 存储引擎》
  2. 《高性能 MySQL》
  3. 《MySQL实战45讲 | 极客时间 | 丁奇》

如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!