在平时的操作中,经常使用count进行操作,计算统计的数据。那么具体的原理是如何的?为什么有时候执行count很慢。
count的实现方式
select count(*) from student;
对于MyISAM引擎来说,会把一个表的总行数存储在磁盘上,因此执行count(*)的时候直接返回,效率高。
但是对于InnoDB引擎来说,执行count ()的时候,需要把数据一行一行从引擎中读出来,然后累积计数。
但是因为InnoDB支持事务以及并发能力,所以大多数业务都选择是InnoDB存储引擎。
为什么数据越来越多的时候,InnoDB不存储一个总行数直接返回呢,那么因为在不同的隔离级别下,每个事务所看到的数据是不一样的。
比如针对如下,开启三个会话,因为MVCC的原因,返回的行数是不一样的。
会话A:因为当前开始一个事务,回话B、C对于A是不可见的。所以返回1W
会话B:会话C插入一行自动提交,所以当会话B自己在插入一条数据的时候,可以查到2条记录,所以就是10002行。
会话C:因为会话B没有提交事务,所以只能看到自己本次的新增记录,所以就是10001行。
根本原因在于:和InnoDB的事务设计有关系,通过多版本并发控制,每一行记录需要判断对自己是否可见,所以只能一行行判断
做的一点优化
主键索引树保存的是数据,普通索引树保存的是主键值,因此普通索引树要比主键索引树小很多,所以对于count(*) 来说,遍历哪个树结果都是一样的,为了尽量减少扫描的数据量,会使用最小的那颗树进行统计遍历。
Count(主键)
在统计count函数多少记录时,mysql的server层维护一个count的变量。每循环从innodb读取一行记录,并且count函数指定的参数不为null ,就将变量count+1。所以当一个表只有主键时,会从主键索引树上进行查询,当主键和普通索引都存在时,会从普通索引树上进行查询,因为这样遍历二级索引的IO成本比遍历主键索引的IO成本小很多。因此优化器优先选择的是二级索引。
Count(1)
select count(1) from t_order;
统计的是这个表里有多少记录
count(id) 和 count(1)的 区别其实就是看是否读取数据的记录内容,count(1)因为是直接判断1,所以只需要统计对应有多少记录就可以,但是count(id) 需要获取的行记录的id 并且不为空 才会进行总数计算。
Count(*)
count(*) 其实mysql会将参数 转换成0来处理
InnoDB handles SELECT COUNT(*) and SELECT COUNT(1) operations in the same way. There is no performance difference.
翻译:InnoDB以相同的方式处理SELECT COUNT(*)和SELECT COUNT(1)操作,没有性能差异。
所以count(1) = count(*) = count(0)
count(字段)
select count(name) from t_order;
统计这个表里有多少行name不为null的记录。性能最差,会以全表扫描的方式进行处理。
小结
count1、count * 、count(id) 在执行的时候,如果表里有二级索引,优化器优先选择二级索引进行扫描。
所以,如果要执行 count(1)、 count(*)、 count(主键字段) 时,尽量在数据表上建立二级索引,这样优化器会自动采用 key_len 最小的二级索引进行扫描,相比于扫描主键索引效率会高一些。
再来,就是不要使用 count(字段) 来统计记录个数,因为它的效率是最差的,会采用全表扫描的方式来统计。如果你非要统计表中该字段不为 NULL 的记录个数,建议给这个字段建立一个二级索引。