从头开始搞懂 MySQL(08)count(*) 很慢怎么办
Posted 一起来搬砖呀
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从头开始搞懂 MySQL(08)count(*) 很慢怎么办相关的知识,希望对你有一定的参考价值。
在开发系统的时候,我们可能经常需要计算一个表的行数,我们一般都是用 select count(*) from table
来查询,如果系统中记录越来越多,这条 SQL 也越来越慢。为什么 mysql 不直接记录一个总数,每次要查询的时候直接读出来呢?我们先来了解一下 count(*) 语句是怎么实现的,为什么这么实现
1、count(*) 的实现方式
在不同的引擎中,count(*) 会有不同的实现方式
MyISAM
MyISAM 引擎将一个表的总行数存在了磁盘上,所以执行 count(*) 的时候会直接返回这个个数,效率很高。如果加了 where 条件的话,MyISAM 返回的也不能返回这么快
InnoDB
InnoDB 执行 count(*) 的时候会将数据一行行的从引擎中读出来然后累积计数
InnoDB 之所以不跟 MyISAM 一样直接把表的行数存起来,是因为 InnoDB 的事务默认隔离级别是可重复读, 在代码中是通过 MVCC(多版本并发控制)的来实现的,每行记录都要进行判断在当前事务中是否可见,所以 InnoDB 只能把数据一行行的从表中读取出来,可见的行才是基于当前查询的总行数
虽然 InnoDB 是一行行读取数据,但是 MySQL 在 count(*) 的时候还是做了一些优化的
InnoDB 主键索引树的叶子节点是数据,普通索引树的叶子结点是主键值,所以,普通索引树会比主键索引树小很多,count(*) 在遍历哪个索引树得到的结果逻辑上都是一样的,MySQL 优化器会找到最小的那个树来遍历,尽量减少扫描的数量。
MyISAMySQL 和 InnoDB 的区别
- MyISAM count(*) 比较快,但是不支持事务
- InnoDB 表 count(*) 会遍历全表,虽然结果准确但是会导致性能问题
2、保存计数的方法
如果我们的系统经常需要展示总数的话,MyISAM 和 InnoDB 又各有缺点,我们可以自己计数,找一个地方将表的行数存起来
2.1 用缓存系统保存计数
我们可以使用 Redis 来保存表的总行数,每插入一条数据计数就加1,删除一行就减 1,这样的话读和更新操作都会很快
但是 Redis 缓存不仅可能会丢失更新,如果在我们向数据表中插入一条数据,Redis 中保存的值也加 1,Redis 异常重启了,重启后我们从 Redis 中把这个值读回来,这个加 1 的计数操作却丢失了。异常重启的话我们可以再重新执行一次 count(*) 获取行数,再将这个值写入到 Redis 中
除了可能会丢失更新,在我们并发的时候,因为我们无法精确控制不同线程的执行时刻,所以可能会出现 Redis 计数不精确的情况
先改 Redis 记录,再写数据表,查询到的数据中可能没有最新插入的,但是 Redis 的计数里面已经加 1 了
先写数据表,再更新 Redis 记录,可能会出现查询到的数据中已经显示最新插入的记录,但是 Redis 的计数还没有加上,出现数据不一致的情况
2.2 在数据库保存计数
用 Redis 来保存 count(*) 查出来的计数可能会有数据丢失或者数据不一致的问题,我们可不可以使用 MySQL 单独设计一张表来存储
首先 InnoDB 是支持崩溃恢复不丢失数据的,而且数据不一致的问题,InnoDB 支持事务,在它的默认隔离级别下,查询使用的是快照读,这样就可以保证在一个查询中,查询数据和数量的结果在逻辑上是一致的
如果我们用事务来保证计数准确,在并发时考虑到系统性能,在事务中我们应该先插入操作记录再更新计数表,InnoDB 语句执行时加锁,事务提交时释放锁,更新计数的语句后执行以缩短持有锁的时间
3、count(*)、count(主键)、count(字段)、count(1)
InnoDB 引擎下,SQL 语句使用 count(*)、count(主键)、count(字段)、count(1) 这些不同的 count() 有什么差别
count() 是一个聚合函数,对于返回的结果集一行行进行判断,如果 count 函数的参数不是 null,累计值就 加 1,最后返回累计值
所以,count(*)、count(主键)、count(1) 都返回的是满足条件的结果集的总行数,count(字段) 返回的是该字段不为 null 的总行数
- count(字段) 如果字段定义的是 not null 的话,会一行行从记录中读出这个字段,判断不能为 null 后按行累加,如果字段定义的可以为 null,会先把值取出来再判断一下,不是 null 才会累加
- count(主键) InnoDB 会遍历整张表,把每行的主键取出来,返回给 server 层,判断不为空后再按行累加
- count(1) InnoDB 也会遍历整张表,但不取值,server 层对返回的每一行,放一个数字 1 进去,判断不能为空后按行累加,count(1) 比 count(主键) 效率要高,使用主键会涉及到解析数据行,拷贝字段值的操作
- count(*) 专门做了优化,不会把全部字段取出来,不取值,count(*) 肯定不是 null,直接按行累加
按照效率排序的话,count(*) ≈ count(1) > count(主键) > count(字段),我们可以尽量使用 count(*)
以上是关于从头开始搞懂 MySQL(08)count(*) 很慢怎么办的主要内容,如果未能解决你的问题,请参考以下文章