MySQL统计总数就用count(*),别花里胡哨的《死磕MySQL系列 十》
有一个问题是这样的统计数据总数用count(*)、count(主键ID)、count(字段)、count(1)那个效率高。
先说结论,不用那么花里胡哨遇到统计总数全部使用count(*).
但是有很多小伙伴就会问为什么呢?本期文章就解决大家的为什么。

系列文章
五分钟,让你明白MySQL是怎么选择索引《死磕MySQL系列 六》
一、不同存储引擎的做法
你需要知道的是在不同的存储引擎下,MySQL对于使用count(*)返回结果的流程是不一样的。
在Myisam中,每张表的总行数都会存储在磁盘上,因此执行count(*)时,是直接从磁盘拿到这个值返回,效率是非常高的。但你也要知道如果加了条件的统计总数返回也不会那么快的。
在Innodb引擎中,执行count(*),需要把数据一行一行的读出来,然后再统计总数返回。
问题:为什么Innodb不跟Myisam一样把表总数存起来呢?
这个问题就需要追溯的我们之前的MVCC文章,就是因为要实现多版本并发控制,才会导致Innodb不能直接存储表总数。
因为每个事务获取到的一致性视图都是不一样的,所以返回的数据总数也是不一致的。
如果你无法理解,再回到MVCC文章好好看看,意思就跟不同事务看到的数据不一致一回事。
实战案例
假设这三个用户是并行的,你会看到三个用户看到最终的数据总数都不一致。
每个用户会根据read view存储的数据来判断那些数据是自己可以看见的,那些是看不见的。

read view
当执行SQL语句查询时会产生一致性视图,也就是read-view,它是由查询的那一时间所有未提交事务ID组成的数组,和已经创建的最大事务ID组成的。
在这个数组中最小的事务ID被称之为min_id,最大事务ID被称之为max_id,查询的数据结果要根据read-view做对比从而得到快照结果。
于是就产生了以下的对比规则,这个规则就是使用当前的记录的trx_id跟read-view进行对比,对比规则如下。
如果落在trx_id<min_id,表示此版本是已经提交的事务生成的,由于事务已经提交所以数据是可见的
如果落在trx_id>max_id,表示此版本是由将来启动的事务生成的,是肯定不可见的
若在min_id<=trx_id<=max_id时
如果row的trx_id在数组中,表示此版本是由还没提交的事务生成的,不可见,但是当前自己的事务是可见的 如果row的trx_id不在数组中,表明是提交的事务生成了该版本,可见
二、MySQL对count(*)做了什么优化
先来看两个索引结构,一个是主键索引、另一个是普通索引。


现在你应该知道了,主键索引的叶子节点存储的是整行数据,而普通索引叶子节点存储的是主键值。
得出结论就是普通索引的比主键索引会小很多。
所以,MySQL对于count(*)这样的操作,不管遍历那个索引树得到的结果在逻辑上都一样。
因此,优化器会找到最小的那棵树来遍历,在保证正确的逻辑前提下,尽量减少扫描数据量,是数据库系统设计的通用法则之一。
问题:为什么存储的有数据怎么不用?
这个图的数据怎么得到的,我想你应该知道了,没错,就是执行show table status \G;
得来的。

那为什么innodb存储引擎不直接使用Rows这个值呢?
还记不记得在第六期文章中,五分钟,让你明白MySQL是怎么选择索引《死磕MySQL系列 六》
先不要返回去看这篇文章,看下上文图中最后查到的数据总条数是多少。
你会发现这两个统计的数据是不一致的,因此这个值肯定是不可以用的。
具体原因
因为Rows这个值跟索引基数Cardinality
一样,都是通过采样统计的。
采样规则
首先,会选出N个数据页,然后统计每个数据页上不同的值,最后得到一个平均值。再用这个平均值乘索引的数据页总数得到的就是索引基数。
并且这个索引基数也不是一成不变的,会随着数据持续增删改,当变更的数据超过1/M时才会触发,M值是根据MySQL参数innodb_stats_persistent得到的,设置为on是10,off是16。
在MySQL8.0这个默认值为on,也就是说当这张表的数据变更超过总数据的1/10就会重新触发采样统计。
三、不同count的用法
以下所有的结论都基于MySQL的Innodb存储引擎。
count(主键ID)
innodb引擎会遍历整张表,把每一行的ID值都那出来,然后返回给server层,server层拿到ID后,判断不可能为空,进行累加。
count(1)
同样遍历整张表,但不取值,server层对返回的每一行,放一个数字1进去,判断是不可能为空的,按行累加。
count(字段)
分为两种情况,字段定义为not null和null
为not null时:逐行从记录里面读出这个字段,判断不能为null,累加 为 null时:执行时,判断到有可能是null,还要把值取出来再判断一下,不是null才累加。
count(*)
这个哥们就厉害了,不是带了*就把所有值取出来,而是MySQL做了专门的优化,count ( * )肯定不是null,按行累加。
结论
按照效率的话,字段 < 主键ID < 1 ~ ,最好都使用count(),别花里胡哨的。
五、总结
本期文章就一句话,统计总数就用count(*),别花里胡哨的
。
“
坚持学习、坚持写作、坚持分享是咔咔从业以来所秉持的信念。愿文章在偌大的互联网上能给你带来一点帮助,我是咔咔,下期见。
”
MySQL统计总数就用count(*),别花里胡哨的《死磕MySQL系列 十》的更多相关文章
- 为什么MySQL字符串不加引号索引失效?《死磕MySQL系列 十一》
群里一个小伙伴在问为什么MySQL字符串不加单引号会导致索引失效,这个问题估计很多人都知道答案.没错,是因为MySQL内部进行了隐式转换. 本期文章就聊聊什么是隐式转换,为什么会发生隐式转换. 系列文 ...
- 打开order by的大门,一探究竟《死磕MySQL系列 十二》
在日常开发工作中,你一定会经常遇到要根据指定字段进行排序的需求. 这时,你的SQL语句类似这样. select id,phone,code from evt_sms where phone like ...
- 重重封锁,让你一条数据都拿不到《死磕MySQL系列 十三》
在开发中有遇到很简单的SQL却执行的非常慢,甚至只查询一行数据. 咔咔遇到的只有两种情况,一种是MySQL服务器CPU占用率很高,所有的SQL都执行的很慢直到超时,程序也直接502,另一种情况是行锁造 ...
- 闯祸了,生成环境执行了DDL操作《死磕MySQL系列 十四》
由于业务随着时间不停的改变,起初的表结构设计已经满足不了如今的需求,这时你是不是想那就加字段呗!加字段也是个艺术活,接下来由本文的主人咔咔给你吹. 试想一下这个场景 事务A在执行一个非常大的查询 事务 ...
- 为什么不建议给MySQL设置Null值?《死磕MySQL系列 十八》
大家好,我是咔咔 不期速成,日拱一卒 之前ElasticSearch系列文章中提到了如何处理空值,若为Null则会直接报错,因为在ElasticSearch中当字段值为null时.空数组.null值数 ...
- 字符串可以这样加索引,你知吗?《死磕MySQL系列 七》
系列文章 三.MySQL强人"锁"难<死磕MySQL系列 三> 四.S 锁与 X 锁的爱恨情仇<死磕MySQL系列 四> 五.如何选择普通索引和唯一索引&l ...
- 五分钟,让你明白MySQL是怎么选择索引《死磕MySQL系列 六》
系列文章 二.一生挚友redo log.binlog<死磕MySQL系列 二> 三.MySQL强人"锁"难<死磕MySQL系列 三> 四.S 锁与 X 锁的 ...
- 一生挚友redo log、binlog《死磕MySQL系列 二》
系列文章 原来一条select语句在MySQL是这样执行的<死磕MySQL系列 一> 一生挚友redo log.binlog<死磕MySQL系列 二> 前言 咔咔闲谈 上期根据 ...
- MySQL强人“锁”难《死磕MySQL系列 三》
系列文章 一.原来一条select语句在MySQL是这样执行的<死磕MySQL系列 一> 二.一生挚友redo log.binlog<死磕MySQL系列 二> 前言 最近数据库 ...
随机推荐
- MacOS下terminal防止ssh自动断开的方法和自动断开的原因
之前换了个工作环境,用terminal连接远程服务器的时候老是出现自动断开的情况,搞得我很是郁闷.因为之前在家的时候,并没有出现过类似情况.后来在网上找了很久,发现国外网站上有个大神说应该是有些路由器 ...
- 面试官问:App测试和Web测试有什么区别?
WEB 测试和 App 测试从流程上来说,没有区别.都需要经历测试计划方案,用例设计,测试执行,缺陷管理,测试报告等相关活动. 从技术上来说,WEB 测试和 APP 测试其测试类型也基本相似,都需要进 ...
- java统一返回标准类型
一.前言.背景 在如今前后端分离的时代,后端已经由传统的返回view视图转变为返回json数据,此json数据可能包括返回状态.数据.信息等......因为程序猿的习惯不同所以返回json数据的格式也 ...
- Node.js躬行记(12)——BFF
BFF字面意思是服务于前端的后端,我的理解就是数据聚合层.我们组在维护一个后台管理系统,会频繁的与数据库交互. 过去为了增删改查会写大量的对应接口,并且还需要在Model.Service.Router ...
- 深入理解Java虚拟机之JVM内存布局篇
内存布局**** JVM内存布局规定了Java在运行过程中内存申请.分配.管理的策略,保证了JVM的稳定高效运行.不同的JVM对于内存的划分方式和管理机制存在部分差异.结合JVM虚拟机规范,一起来 ...
- $hadow$ocks与Privoxy基础原理
$hadow$ocks与Privoxy基础原理 以下所有提到$hadow$ocks的均以ss指代 为什么要用ss呢? 在早期(如今绝大多数也是),对于互联网的访问流程是及其简单的:浏览器(或其他客户端 ...
- 我们携手啦 | SphereEx 正式加入 openGauss 社区
近日,SphereEx 签署 CLA ( Contribution License Agreement,贡献许可协议),正式加入 openGauss 社区. SphereEx 和 openGauss ...
- postman如何解决下个接口依赖上一个接口数据
解决思路:将上一个接口返回的数据保存起来,然后提供下个接口使用,postman中通过设置全局变量来保存数据 步骤1:在Tests中添加如下图js代码. var jsondata = JSON.pars ...
- 告别Vuex,发挥compositionAPI的优势,打造Vue3专用的轻量级状态
Vuex 的遗憾 Vuex 是基于 Vue2 的 option API 设计的,因为 optionAPI 的一些先天问题,所以导致 Vuex 不得不用各种方式来补救,于是就出现了 getter.mut ...
- 初学Python-day12 装饰器函数
装饰器 1.概念 本质就是一个Python函数,其他函数在本身不变的情况下去增加额外的功能,装饰器的返回值是一个函数. 常用的场景:插入日志,事务处理,缓存,权限校验等. 2.普通函数回顾 1 def ...