在开发中,我们肯定经常会遇到统计表总记录数的需求。在selectcount(?)fromt、count(*)、count(primarykeyid)、count(field)、count(1)等查询语句中如何选择?在阿里巴巴制定的MySQL规范中,规范count使用的规范有4条。我们来查一查:【强制】不要用count(列名)或count(常量)来代替count(*),count(*)是SQL92定义的统计行的标准语法,与数据库,与NULL和非NULL无关。说明:count(*)会统计值为NULL的行,count(列名)不会统计值为NULL的行。那么今天我们就来聊一聊这些不同的统计方法是如何实现的,它们之间的性能差异有哪些。count()函数首先我们需要了解count()的语义。count()是一个聚合函数。对于返回的结果集,逐行判断。如果count函数的参数不为null,则累计值加1,否则不加。最后返回累加值。如果没有则返回0。count(*)、count(primarykeyid)和count(1)都是表示结果集中返回的满足条件的总行数;whilecount(field)表示返回的数据行满足条件,且参数"field"不为null的总数。接下来,我们详细分析每种统计方法的性能差异。count(*)我们在分析一条SQL语句的执行时,一定不能脱离存储引擎。在不同的MySQL存储引擎中,count(*)有不同的实现。MyISAM引擎将一张表的总行数存储在磁盘上,所以在执行count(*)时,直接返回磁盘上的行数就可以了,效率很高。InnoDB引擎需要逐行从引擎中读取数据,然后累加计数。这时候你是不是在想,MySQL建表的存储引擎已经默认为InnoDB了,而且很多MySQL相关书籍也提到InnoDB对于特殊场景来说并不是必须的。但是当我们使用count(*)进行统计时,效率会随着表记录的增加而越来越慢。为什么InnoDB不能像MyISAM那样预先存储记录总数?这里主要是因为InnoDB是支持事务的,由于MVCC,在某个时刻InnoDB表应该返回多少行是不确定的。它必须根据当前事务隔离级别判断某条记录对当前事务是否可见。InnoDB虽然需要逐行统计,但是MySQL也做了优化。通过上一篇索引文章介绍的知识,我们知道InnoDB分为主键索引树和普通索引树。普通索引树的叶子节点是主键值而不是行记录。所以普通的索引树要比主键索引树小很多,所以count(*)在遍历索引树的时候会找最小的树遍历。在保证逻辑正确的前提下,尽量减少扫描数据量是数据库系统设计的一般原则之一。count(*)并没有取出所有的字段,而是对其进行了优化,不取值。count(*)的参数不能为null,累加行后返回结果。count(primarykeyid)count(primarykeyid),InnoDB引擎会遍历整张表,取出每一行的id值,传给server层。server层拿到引擎给的id值后,判断不可能为空,逐行累加。count(1)count(1),InnoDB引擎遍历整张表,但不取值。server层为返回的每一行存储一个数字1,判断不可能为空,逐行累加。count(field)和count(field)字段,我们需要分析两种情况下的参数字段。如果这个参数字段不允许为null(notnull),那么InnoDB引擎会逐行从记录中读取这个字段,如果判断不能为null,则逐行累加。如果允许参数字段为null,那么在执行过程中,判断可能为null时,必须把值取出来重新判断,不为null才累加。总结Count(field)和count(primarykeyid)需要引擎返回值,涉及到解析数据行和复制字段,性能比另外两个差。但是count(field)还需要判断field参数是否为null。如果判断操作多了,性能会比count(主键id)差。count(1)和count(*)都不涉及取值,所以性能差别不大。所以结论是按照查询效率排序的结果为:count(field)
