一个架构师来公司看我用count(*)统计数据总数。告诉我,怎么用count(*)统计数据,count(*)太慢了,万一数据库毁了怎么办,用count(1)。吓得赶紧改成count(1)。count(1)的性能比count(*)高吗?记得有一次面试的时候,面试官也问过我这样一个问题。count(*)还是count(1)哪个更有效?今天我们就来说说count(1)和count(*)的效率。我们不知道不同存储引擎的性能。Mysql有两种常见的存储引擎,MyISAM和Innodb。在这两种存储引擎下,MySQL使用count(*)返回结果的方式不同。在MyISAM引擎中,每张表的总行数是存储在磁盘上的,所以在执行count(*)时,直接从磁盘中获取值并快速返回。但是如果后面加上where查询条件,总的统计就没有想象的那么快了。Innodb引擎中,要执行count(*),需要逐行读取数据,然后统计总数。看到这里,不知道大家有没有这样的疑问:为什么Innodb引擎不像MyISAM引擎那样存储总表记录呢?这个问题问得好。在回答这个问题之前,我们先了解一下MVCC。MVCC的全称是什么:Multi-VersionConcurrencyControl就是多版本并发控制,MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问;在编程语言中实现事务性内存。MySQLInnoDB中MVCC的实现,主要是为了提高数据库的并发性能,更好的处理读写冲突,这样即使有读写冲突,也能实现非锁和非阻塞并发读取。正是因为需要实现多版本并发控制,所以Innodb不能直接存储表的记录总数。因为每次事务获取的一致视图不同,所以返回数据的总记录也不一致。举个例子来说明:如果有一个用户表tb_user,有3个地方在查询用户总数。selectcount(*)fromtb_user这时候每次查到的用户总数可能不一样。这是因为每个用户都会根据读取视图中存储的数据来判断哪些数据对他可见,哪些不可见。读视图在执行SQL语句查询时,会生成一个一致性视图,即read-view,它由查询时刻所有未提交的事务ID和已经创建的最大事务ID组成的数组组成。这个数组中最小的事务ID叫做min_id,最大的事务ID叫做max_id,根据read-view比较查询数据结果得到一个snapshot。因此,产生了以下比较规则。这条规则是用当前记录trx_id来和read-view进行比较。规则如下:如果落在trx_idmax_id,则说明该版本是由以后启动的事务生成的。它是看不见的。如果落在min_id和max_id的中间(min_id<=trx_id<=max_id),如果是row数组,如果trx_id在数组中,说明该版本是由一个尚未提交的事务生成的,它不可见,但是当前自己的交易是可见的;如果该行的trx_id不在数组中,则说明提交的事务生成的版本是可见的。看完这里,相信你已经知道为什么Innodb引擎不像MyISAM引擎那样存储总表记录了。因为InnoDB支持事务,MyISAM不支持事务。在执行count(*)操作时仍然进行了优化。mysql优化了count(*)InnoDB是索引组织表,主键索引树的叶子节点是数据,普通索引树的叶子节点是主键值。因此,普通索引树比主键索引树小很多。对于count(*)等操作,遍历哪棵索引树得到的结果在逻辑上是相同的。因此,MySQL优化器会寻找最小的树进行遍历。如果你使用过showtablestatus命令,你会发现这个命令的输出还有一个rows值来显示该表当前有多少行。那么,这个rows值能不能代替count(*)呢?其实不行,行数是抽样估计出来的,所以不准确。到什么程度是不准确的,官方文档说是40%到50%。所以showtablestatus命令显示的行数不能直接使用。基于MySQL的Innodb存储引擎,以下4种统计表记录总数的方式,哪种方式效率最高?在一个实际案例中,准备了一张超过500万条数据的表。表结构如下:CREATETABLE`tb_user`(`id`int(11)unsignedNOTNULLAUTO_INCREMENT,`user_id`int(11)DEFAULTNULL,`user_name`varchar(100)DEFAULTNULL,PRIMARYKEY(`id`)USINGBTREE,UNIQUEKEY`userId`(`user_id`)USINGBTREE)ENGINE=InnoDBAUTO_INCREMENT=1DEFAULTCHARSET=utf8mb4可以看到,这张表有主键索引,通过不同的方式查询总表中的用户记录数。Count(主键id)使用selectcount(*)fromtb_user,耗时0.739s。InnoDB引擎会遍历整张表,取出每一行的id值,返回给server层。server层拿到id后,判断不可能为空,按行累加。count(1)使用selectcount(1)fromtb_user在0.753s内遍历全表,但不取值。server层在返回的每一行中都放上一个数字1,判断不可能为空。按行加起来。count(field)使用selectcount(user_name)fromtb_user,耗时1.436s,分为两种情况。当字段定义为notnull且null不为null时:逐行从记录中读取该字段,判断不能为null,累加为null时:执行时判断可能为null,并且必须把值取出来重新判断。如果它不为空,则将累加count(*)。使用selectcount(*)fromtb_user需要0.739s。需要注意的是,mysql没有把所有的值都用*取出来,做了特殊的优化,count(*)肯定不为null,而且是按行累加的。从上面的执行结果我们知道count(field)