赞
踩
在开发系统的时候,我们可能经常需要计算一个表的行数,我们一般都是用 select count(*) from table
来查询,如果系统中记录越来越多,这条 SQL 也越来越慢。为什么 MySQL 不直接记录一个总数,每次要查询的时候直接读出来呢?我们先来了解一下 count(*) 语句是怎么实现的,为什么这么实现
在不同的引擎中,count(*) 会有不同的实现方式
MyISAM
MyISAM 引擎将一个表的总行数存在了磁盘上,所以执行 count(*) 的时候会直接返回这个个数,效率很高。如果加了 where 条件的话,MyISAM 返回的也不能返回这么快
InnoDB
InnoDB 执行 count(*) 的时候会将数据一行行的从引擎中读出来然后累积计数
InnoDB 之所以不跟 MyISAM 一样直接把表的行数存起来,是因为 InnoDB 的事务默认隔离级别是可重复读, 在代码中是通过 MVCC(多版本并发控制)的来实现的,每行记录都要进行判断在当前事务中是否可见,所以 InnoDB 只能把数据一行行的从表中读取出来,可见的行才是基于当前查询的总行数
虽然 InnoDB 是一行行读取数据,但是 MySQL 在 count(*) 的时候还是做了一些优化的
InnoDB 主键索引树的叶子节点是数据,普通索引树的叶子结点是主键值,所以,普通索引树会比主键索引树小很多,count(*) 在遍历哪个索引树得到的结果逻辑上都是一样的,MySQL 优化器会找到最小的那个树来遍历,尽量减少扫描的数量。
如果我们的系统经常需要展示总数的话,MyISAM 和 InnoDB 又各有缺点,我们可以自己计数,找一个地方将表的行数存起来
我们可以使用 Redis 来保存表的总行数,每插入一条数据计数就加1,删除一行就减 1,这样的话读和更新操作都会很快
但是 Redis 缓存不仅可能会丢失更新,如果在我们向数据表中插入一条数据,Redis 中保存的值也加 1,Redis 异常重启了,重启后我们从 Redis 中把这个值读回来,这个加 1 的计数操作却丢失了。异常重启的话我们可以再重新执行一次 count(*) 获取行数,再将这个值写入到 Redis 中
除了可能会丢失更新,在我们并发的时候,因为我们无法精确控制不同线程的执行时刻,所以可能会出现 Redis 计数不精确的情况
先改 Redis 记录,再写数据表,查询到的数据中可能没有最新插入的,但是 Redis 的计数里面已经加 1 了
先写数据表,再更新 Redis 记录,可能会出现查询到的数据中已经显示最新插入的记录,但是 Redis 的计数还没有加上,出现数据不一致的情况
用 Redis 来保存 count(*) 查出来的计数可能会有数据丢失或者数据不一致的问题,我们可不可以使用 MySQL 单独设计一张表来存储
首先 InnoDB 是支持崩溃恢复不丢失数据的,而且数据不一致的问题,InnoDB 支持事务,在它的默认隔离级别下,查询使用的是快照读,这样就可以保证在一个查询中,查询数据和数量的结果在逻辑上是一致的
如果我们用事务来保证计数准确,在并发时考虑到系统性能,在事务中我们应该先插入操作记录再更新计数表,InnoDB 语句执行时加锁,事务提交时释放锁,更新计数的语句后执行以缩短持有锁的时间
InnoDB 引擎下,SQL 语句使用 count(*)、count(主键)、count(字段)、count(1) 这些不同的 count() 有什么差别
count() 是一个聚合函数,对于返回的结果集一行行进行判断,如果 count 函数的参数不是 null,累计值就 加 1,最后返回累计值
所以,count(*)、count(主键)、count(1) 都返回的是满足条件的结果集的总行数,count(字段) 返回的是该字段不为 null 的总行数
按照效率排序的话,count(*) ≈ count(1) > count(主键) > count(字段),我们可以尽量使用 count(*)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。