有一个问题是这样的统计数据总数用count(*)、count(主键ID)、count(字段)、count(1)那个效率高。

先说结论,不用那么花里胡哨遇到统计总数全部使用count(*).

但是有很多小伙伴就会问为什么呢?本期文章就解决大家的为什么。

系列文章

五分钟,让你明白MySQL是怎么选择索引《死磕MySQL系列 六》

字符串可以这样加索引,你知吗?《死磕MySQL系列 七》

无法复现的“慢”SQL《死磕MySQL系列 八》

什么?还在用delete删除数据《死磕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系列 十》的更多相关文章

  1. 为什么MySQL字符串不加引号索引失效?《死磕MySQL系列 十一》

    群里一个小伙伴在问为什么MySQL字符串不加单引号会导致索引失效,这个问题估计很多人都知道答案.没错,是因为MySQL内部进行了隐式转换. 本期文章就聊聊什么是隐式转换,为什么会发生隐式转换. 系列文 ...

  2. 打开order by的大门,一探究竟《死磕MySQL系列 十二》

    在日常开发工作中,你一定会经常遇到要根据指定字段进行排序的需求. 这时,你的SQL语句类似这样. select id,phone,code from evt_sms where phone like  ...

  3. 重重封锁,让你一条数据都拿不到《死磕MySQL系列 十三》

    在开发中有遇到很简单的SQL却执行的非常慢,甚至只查询一行数据. 咔咔遇到的只有两种情况,一种是MySQL服务器CPU占用率很高,所有的SQL都执行的很慢直到超时,程序也直接502,另一种情况是行锁造 ...

  4. 闯祸了,生成环境执行了DDL操作《死磕MySQL系列 十四》

    由于业务随着时间不停的改变,起初的表结构设计已经满足不了如今的需求,这时你是不是想那就加字段呗!加字段也是个艺术活,接下来由本文的主人咔咔给你吹. 试想一下这个场景 事务A在执行一个非常大的查询 事务 ...

  5. 为什么不建议给MySQL设置Null值?《死磕MySQL系列 十八》

    大家好,我是咔咔 不期速成,日拱一卒 之前ElasticSearch系列文章中提到了如何处理空值,若为Null则会直接报错,因为在ElasticSearch中当字段值为null时.空数组.null值数 ...

  6. 字符串可以这样加索引,你知吗?《死磕MySQL系列 七》

    系列文章 三.MySQL强人"锁"难<死磕MySQL系列 三> 四.S 锁与 X 锁的爱恨情仇<死磕MySQL系列 四> 五.如何选择普通索引和唯一索引&l ...

  7. 五分钟,让你明白MySQL是怎么选择索引《死磕MySQL系列 六》

    系列文章 二.一生挚友redo log.binlog<死磕MySQL系列 二> 三.MySQL强人"锁"难<死磕MySQL系列 三> 四.S 锁与 X 锁的 ...

  8. 一生挚友redo log、binlog《死磕MySQL系列 二》

    系列文章 原来一条select语句在MySQL是这样执行的<死磕MySQL系列 一> 一生挚友redo log.binlog<死磕MySQL系列 二> 前言 咔咔闲谈 上期根据 ...

  9. MySQL强人“锁”难《死磕MySQL系列 三》

    系列文章 一.原来一条select语句在MySQL是这样执行的<死磕MySQL系列 一> 二.一生挚友redo log.binlog<死磕MySQL系列 二> 前言 最近数据库 ...

随机推荐

  1. MacOS下terminal防止ssh自动断开的方法和自动断开的原因

    之前换了个工作环境,用terminal连接远程服务器的时候老是出现自动断开的情况,搞得我很是郁闷.因为之前在家的时候,并没有出现过类似情况.后来在网上找了很久,发现国外网站上有个大神说应该是有些路由器 ...

  2. 面试官问:App测试和Web测试有什么区别?

    WEB 测试和 App 测试从流程上来说,没有区别.都需要经历测试计划方案,用例设计,测试执行,缺陷管理,测试报告等相关活动. 从技术上来说,WEB 测试和 APP 测试其测试类型也基本相似,都需要进 ...

  3. java统一返回标准类型

    一.前言.背景 在如今前后端分离的时代,后端已经由传统的返回view视图转变为返回json数据,此json数据可能包括返回状态.数据.信息等......因为程序猿的习惯不同所以返回json数据的格式也 ...

  4. Node.js躬行记(12)——BFF

    BFF字面意思是服务于前端的后端,我的理解就是数据聚合层.我们组在维护一个后台管理系统,会频繁的与数据库交互. 过去为了增删改查会写大量的对应接口,并且还需要在Model.Service.Router ...

  5. 深入理解Java虚拟机之JVM内存布局篇

    内存布局**** ​ JVM内存布局规定了Java在运行过程中内存申请.分配.管理的策略,保证了JVM的稳定高效运行.不同的JVM对于内存的划分方式和管理机制存在部分差异.结合JVM虚拟机规范,一起来 ...

  6. $hadow$ocks与Privoxy基础原理

    $hadow$ocks与Privoxy基础原理 以下所有提到$hadow$ocks的均以ss指代 为什么要用ss呢? 在早期(如今绝大多数也是),对于互联网的访问流程是及其简单的:浏览器(或其他客户端 ...

  7. 我们携手啦 | SphereEx 正式加入 openGauss 社区

    近日,SphereEx 签署 CLA ( Contribution License Agreement,贡献许可协议),正式加入 openGauss 社区. SphereEx 和 openGauss ...

  8. postman如何解决下个接口依赖上一个接口数据

    解决思路:将上一个接口返回的数据保存起来,然后提供下个接口使用,postman中通过设置全局变量来保存数据 步骤1:在Tests中添加如下图js代码. var jsondata = JSON.pars ...

  9. 告别Vuex,发挥compositionAPI的优势,打造Vue3专用的轻量级状态

    Vuex 的遗憾 Vuex 是基于 Vue2 的 option API 设计的,因为 optionAPI 的一些先天问题,所以导致 Vuex 不得不用各种方式来补救,于是就出现了 getter.mut ...

  10. 初学Python-day12 装饰器函数

    装饰器 1.概念 本质就是一个Python函数,其他函数在本身不变的情况下去增加额外的功能,装饰器的返回值是一个函数. 常用的场景:插入日志,事务处理,缓存,权限校验等. 2.普通函数回顾 1 def ...