背景
我们在写业务系统的时候列表都是要写分页的,对于分页的列表来说需要两次查询来完成,一次是查询总数,一次是查询一页的具体数据,那么我们会发现列表的查询无形中会产生一些浪费,那就是count执行时,而且会很慢,带limit的查询不会很慢,那为什么慢呢?怎么解决呢?在下面会总结分享。
原理
count(*)的性能跟存储引擎有直接关系,不是所有的存储引擎的性能都差的。mysql常用的引擎是innodb和myisam,myisam会把总行数保存在磁盘上,所以不慢。但是innodb需要支持事务,有MVCC的存在,在同一个时间点不同事务,查询同一个SQL的查询结果是不一定一致的。所以innodb执行count(*)的时候是通过一行行读取累加起来的,数据量只要一大了之后就会比较慢。
解决方案
缓存法(redis或二级缓存guava/caffine)
每次新增或删除某个表的数据时,直接在redis里加1或减1
缺点:不过在写频繁的情况下,可能会存在缓存和数据库的数据不一致的问题。但对于统计浏览总次数或者浏览总人数这种业务场景,对数据的准确性要求并不高,容忍数据不一致的情况存在。
二级缓存在一定时间内相同搜索条件的查询结果直接取缓存
缺点:但是如果使用二级缓存,可能存在不同的服务器上,数据不一样的情况。我们需要根据实际业务场景来选择,没法适用于所有业务场景。
减少复杂度,尽量使用索引
将列表查询的SQL中的核心逻辑保留去count查询,不影响查询条数,但是列表需要显示该字段的部分join去掉,不去关联查询,尽量减少join来提高速度。
缺点:本质上还是通过从库里去查询,但是能保证查询的准确性
几种count的区别
count(主键id):遍历-取值-累加
count(1):遍历-不取值,每行放1-累加
count(字段):字段notnull的情况:遍历-取值-累加
字段有可能为null的情况:遍历-取值去空-累加
count(*):傻瓜式做法,一站式打包好的方式,mysql专门做好优化,不取值,一定不为空,直接累加
执行效率总结:count(*)=count(1)count(id)count(字段),所以尽量使用count(*)
上面便是对count慢的一些总结,有些时候的确是有误区,就比如count(*),不去研究也许会觉得count(主键)或count(1)会更快些。所以以后如果遇到需要优化查询性能的话可以想一想,可以用哪个方案来解决。建议是从sql性能上来处理,缓存方法固然好,但是最好能做成统一的解决方案,如果为某个页面去单独做有点不太值得。个人建议仅供参考。梅花香自苦寒来,只要每天进步一点点,相信有一天能够一骑绝尘的。