赞
踩
在不同引擎中,count(*) 有不同的实现方式
这里是没说过滤条件,加了 where 后,MyISAM 也是不能返回这么快的。
因为即使在同一时刻,由于多版本并发控制(MVCC)的原因,InnoDB 返回多少行也是不确定的。
由于 InnoDB 默认的隔离级别是可重复读,代码上使用的多版本并发控制的。每一行记录都要自己判断可不可见,所以对于 count(*) 来说,只能一行一行的读。
当然 MySQL 还是对 count(*) 操作做了优化。
InnoDB 是索引组织表,普通索引比主键索引小很多。所以 MySQL 优化器会找到最小的那棵树来遍历。
在保证逻辑正确的前提下,尽量减少扫描的数据量,是数据库系统设计的通用法则之一。
使用 show table status
这个命令的话,在结果里有一个 TABLE_ROWS
,这个命令跑的很快,可以代替 count(*) 吗? 不行,这个 TABLE_ROWS
是一个估值,不准。
我们可以自己计数。这些方法的基本思路是:我们自己找一个地方,把操作记录表的行数存起来。
可以使用 Redis 服务来保存这个表的总行数。这个表每次加一减一,对应 redis 上跟着改变。
但是,可能会丢失更新。
Redis的数据不能永久地留在内存里,所以你会找一个地方把这个值定期地持久化存储起来。但即使这样,仍然可能丢失更新。试想如果刚刚在数据表中插入了一行,Redis中保存的值也加了 1,然后Redis异常重启了,重启后你要从存储redis数据的地方把这个值读回来,而刚刚加1的这个计数操作却丢失了。
当然了,这还是有解的。比如,Redis异常重启以后,到数据库里面单独执行一次count(*)获取真 实的行数,再把这个值写回到Redis里就可以了。异常重启毕竟不是经常出现的情况,这一次全 表扫描的成本,还是可以接受的。
即使 Redis 正常工作,这个值还是逻辑上不精确。
因为使用 Redis 计数,就会涉及到两个非原子操作。增、减数据,Redis上的数增、减。
在并发的时候,一个线程要查 count(*),但此时另一个线程刚刚插入一条数据,还没改 Redis。就会造成问题。
如果把这个计数直接放到数据库里单独的一张计数表 C 中,又会怎样呢?
首先,InnoDB 支持崩溃恢复不丢失数据。
并发问题解决了吗?是的,解决了,因为 InnoDB 支持可重复读隔离级别。
会话 B 在 T3 进行查询的时候,由于事务 A 并没有提交,所以这个加一操作对 B 不可见。
因此,会话 B 中看到的结果里,查计数值和“最近100记录”看到的结果一样,逻辑上是一致的。
以下基于 InnoDB 引擎。
count() 的语义:count() 是一个聚合函数,对于返回结果的判断,一行行的判断,如果 count 的参数不是 NULL,累加值就加 1,否则不加。最后返回累计数。
所以 count(*)、count(主键) 和 count(1) 都表示返回满足条件结果集的总行数;而 count(字段) 则返回满足条件的数据行里,参数“字段”不为 NULL 的总个数。
对于 count(主键 id) 来说,InnoDB 引擎会遍历整张表,把每一行的 id 值取出来,返回给 server 层。server 层拿到 id 后,判断不可能为空,就按行累加。
对于 count(1) 来说,InnoDB 引擎会遍历整张表,但不取值。server 层对于返回的每一层,放一个数字“1”进去,判断不可能为空,按行累加。
单说这两个 count() 的话,count(1) 是比 count(主键id) 快,因为不涉及解析行数据,以及拷贝值的操作。
对于 count(字段) 来说:
也就是 server 层要什么字段,InnoDB 就返回什么字段。
但 count(*) 是例外,并不会把字段全部取出来,而是专门做了优化,不取值。count(*) 肯定不是 NULL,按行累加。
按照排序效率的话, c o u n t ( 字 段 ) < c o u n t ( 主 键 i d ) < c o u n t ( 1 ) ≈ c o u n t ( ∗ ) count(字段)\lt count(主键id)\lt count(1) \approx count(*) count(字段)<count(主键id)<count(1)≈count(∗),所以建议使用 count(*)。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。