Mysql 系列 | count(*)
项目中常常需要统计表数据总数,select count(*) from t;
。
在 InnoDB 搜索引擎中,count(*)
需要把数据都读到内存中然后计数,数据很多时会很慢。
count 字段或者 ID,会不会好一些呢!实际上在 Mysql 中,count(字段) < count(主键id) < count(1) ≈ count(*),也就是说 count(*)
是最佳方案。
除此之外,可以考虑把计数保存起来,缓存或者数据库。
count(*) 的实现方式
-
MyISAM 中,会把表的总行数存在磁盘中,可以直接读出来,效率很高。
-
InnoDB 中,有多版本并发控制(MVCC),可重复读时默认隔离级别。每个事务中读取的行数不一定相同,所以每次查询的时候,需要一行一行读取依次判断可见性,并计数。
-
InnoDB 中,count(*) 已经被优化过。优化器会选择最小的索引树遍历,在保证逻辑正确的情况下,尽量减少数据的扫描行数。
-
show table status 命令取到的 table_rows 是采样估算的结果,误差可能达到 40% - 50%,不能直接使用。
缓存系统保存计数
-
把计数存在 Redis 中,表中每添加一条数据,更新一次 Redis 的值
-
数据库中表数据插入过程中,Redis 异常重启,会导致数据丢失。不过可以在重启后,去表中 count 得到正确的数值更新到 Redis 中。
-
Redis 正常工作时,因为 InnoDB 的 MVCC 机制,每个事务应该读到的总行数是不同的。而从 Redis 中则只能读到最新统计的行数,也是不精确的。
数据库保存计数
-
表中新增数据时,更新计数表中的数据
-
因为实在同一存储系统,可以保证逻辑一致性。不过每次需要从表中查得计数值。
不同 count 的用法
count() 是个聚合函数。对于返回的结果集,判断参数不为 NULL 时统计值加一,否则不加。最终返回统计结果。
count(主键 ID)
- InnoDB 会遍历整张表,取出 ID 返回给 Server 层,判断是不可能为空的,然后按行累加。
count(1)
-
遍历整张表,但不取数据。Server 层对于返回的每一行数据,放一个数字 1,判断不可能为空,逐行累加。
-
相比 count(ID) 更快。没有取数据的步骤,减少了从引擎返回 ID 后解析数据,拷贝字段值的操作。
count(字段)
-
字段定义为
not null
,从记录中读出字段,判断不能为空,逐行累加。 -
字段定义为
null
,要把字段取出来,判断不为空,才会累加。
count(*)
- 已经被优化过,不会取值,肯定不为空,逐行累加。