mysql 中 count(*)、count(1)、count(主键ID)、count(列名) 的区别

在平常使用过程中,经常需要用到 “计数” 的功能,但是有时会纠结于是使用 count(*)、count(1)、count(主键ID) 还是 count(列名),这几种查询方式有什么区别呢?本文将对此进行简单的介绍。

按结果集区别

按照查询返回的结果集进行区分,可以将上述四种查询分为两类,一类是返回表中的总记录数(不考虑 where 的过滤条件,如果加上 where 的过滤条件,则是返回过滤后的所有记录数,下同),另一类是返回表中字段不为空的记录数。

  • 返回表中总记录数:count(*)、count(1)、count(主键ID) 都是属于这一类,其会扫描表中所有记录,不管这个记录中是否包含空字段(主键不为空),都会将这条记录返回并进行计数。
  • 返回表中字段不为空的记录数:count(列名) 会自动过滤掉该列的值为空的记录,将非空的记录返回并进行记录

可以看到,如果某个列中存在空值,则 count(*)、count(1)、count(主键ID) 查询得到的结果大于 count(列名) 查询得到的结果。如果某个列中不存在空值,则上述四种查询方式得到的结果相同。

按查询性能区别

分析性能时,按照以下三个原则进行分析

  • server 层需要什么,InnoDB 引擎层就返回什么
  • InnoDB 只给必要的值
  • 现在的优化器只优化了 count(*) 的语义为 “取行数”,其他 “显而易见” 的优化并没有做

四种查询的性能如下

  • count(列名):一行行地从表中读取该字段返回给 server 层,server 层进行判断,如果不为空,则计数加一。
  • count(主键ID):遍历整张表,把每一行的 id 值取出来返回给 server 层,server 层判断到值不为空,计数加一。从这里看,count(主键ID) 与 count(列名) 的做法类似,似乎没有多大的性能差别,但是 count(主键ID) 可以选择最小的索引进行遍历,而 count(列名),如果在该列上没有索引,则只能选择主键的索引进行遍历,这样就多了一层遍历。即使该字段上有索引,但可能该索引并不是最小的索引。因此,整体而言,count(列名) 的查询性能低于count(主键ID)。
  • count(1):会选择最小的索引数进行遍历,但是遍历时并不会取出具体的值,server 层对于返回的每一行,放一个数字 “1” 进去,然后判断该值不为空,计数加一。可以看到,虽然其与 count(主键ID) 一样取最小的索引数进行遍历,但是 count(1) 并不会读取表中的数据值,而 count(主键ID) 需要读取表中的数据值进行返回,其多了一个操作,相比更加耗时。因此,count(1) 的查询性能优于 count(主键ID) 。
  • count(*):mysql 专门对这个查询进行了优化,其会选择最小的索引数进行遍历,同样不会取值,server 层拿到返回的每一行数据,计数加一。

从上述分析可以知,按查询效率而言, count(字段) < count(主键ID) < count(1) ≈ count(*)

总结

从查询结果而言,count(字段) 不会统计字段值为空的记录,count(主键ID)、count(1)、count(*) 会统计字段值为空的记录。从查询性能而言,count(字段) < count(主键ID) < count(1) ≈ count(*)。

因此,对于需要剔除非空字段的统计,使用 count(字段) 进行查询,对于需要统计所有字段(包含非空字段),使用 count(*) 进行查询。

参考资料

[1] 林晓斌.count(*)这么慢,我该怎么办?[J/OL]. https://time.geekbang.org/column/article/72775,2018-12-14