Count这个函数在日常开发中,我们会经常使用,那Count()的原理是什么样?是全表扫描的?用Count(*)还是Count(1)?
全表扫描
在Innodb中,由于事务的存在,如果使用count(*)进行查询表的行数,会出现不同session查询结果不一致的情况。具体如下(默认rr隔离级别):
| sessionA | sessionB |
|---|---|
| SELECT COUNT(*) FROM table; | |
| INSERT TABLE VALUES ... | |
| SELECT COUNT(*) FROM table; | |
| SELECT COUNT(*) FROM table; |
如果数据库的初始表的数量是100,那么sessionA两次查询的结果都是100,因为RR隔离级别下,数据库有不可重复读的概念。而在SessionB中,第二次查询table的结果为101。
因为有MVCC的存在,Innodb没办法想MyIsam一样将表的行数记录起来,等待查询的时候直接取。
在Innodb中查询表的行记录数使用的是全表扫描
全表扫描哪个数据?
Innodb是聚集索引,索引即数据。主键作为索引的B+Tree数据结构中,包含表中所有字段的数据。简单来说,也就是聚集索引在Innodb中是单位数据最大的索引。
我们使用Count(*)查询表的行数使用的是聚集索引,还是非聚集索引呢?
答案是优化器会找到最小的那棵树来遍历。在保证正确的前提下,尽量减少扫描的数据量,是数据库系统设计的通用法则。
开发中,如何查询表的行数
全表扫描这种操作,会产生大量的磁盘IO,所以性能会很差。我们面对这种情况,可以使用单独的一个表来记录数据库表的行数。是否可以使用Redis来保存行数呢?笔者目前也不确定可行性,后续会研究下一下进行补充。
Count(*)和Count(1)...的区别,我们应该用哪个?
首先明确Count()的语义,它是一个聚合函数,对于返回的结果集,一行行地去判断,如果count函数的参数不是NULL,累计值加1,否则不加。最后返回累计值。
- 对于 count(主键 id) 来说,InnoDB 引擎会遍历整张表,把每一行的 id 值都取出来,返回给 server 层。server 层拿到 id 后,判断是不可能为空的,就按行累加。
- 对于 count(1) 来说,InnoDB 引擎遍历整张表,但不取值。server 层对于返回的每一行,放一个数字“1”进去,判断是不可能为空的,按行累加。
单看这两个用法的差别的话,你能对比出来,count(1) 执行得要比 count(主键 id) 快。因为从引擎返回 id 会涉及到解析数据行,以及拷贝字段值的操作。
- 对于 count(字段) 来说: 1.如果这个“字段”是定义为 not null 的话,一行行地从记录里面读出这个字段,判断不能为 null,按行累加; 2.如果这个“字段”定义允许为 null,那么执行的时候,判断到有可能是 null,还要把值取出来再判断一下,不是 null 才累加。
但是 count() 是例外,并不会把全部字段取出来,而是专门做了优化,不取值。count() 肯定不是 null,按行累加。
所以结论是:按照效率排序的话,count(字段)<count(主键 id)<count(1)≈count(),所以我建议你,尽量使用 count()。