为什么 select count(*) from t,在 InnoDB 引擎中比 MyISAM 慢?

统计一张表的总数量,是我们开发中常有的业务需求,通常情况下,我们都是使用 select count(*) from t SQL 语句来完成。随着业务数据的增加,你会发现这条语句执行的速度越来越慢,为什么它会变慢呢?

为什么会变慢?想要得到答案就需要知道 MySQL 是如何统计总数量的,先说一个前提吧,count(*) 的具体实现是由存储引擎实现的,也就是说不同的存储引擎实现的方式不一样。

标题:为什么select count( * ) from t,在 InnoDB 引擎中比 MyISAM 慢?也是高频面试题。

InnoDB和MyISAM 是我们常用的 MySQL 存储引擎,所以主要对比一下 count(*) 在 InnoDB 和 MyISAM 中的实现:

在 MyISAM 存储引擎中,把表的总行数存储在磁盘上,当执行 select count(*) from t 时,直接返回总数据。
在 InnoDB 存储引擎中,跟 MyISAM 不一样,没有将总行数存储在磁盘上,当执行 select count(*
) from t 时,会先把数据读出来,一行一行的累加,最后返回总数量。


知道了 InnoDB 和 MyISAM 引擎 count() 实现之后,为什么select count() from t,在 InnoDB 引擎中比 MyISAM 慢?应该有答案了吧,但是这个结论需要有一个前提,就是统计 SQL 不带过滤条件。如果 统计数量 SQL 语句为:select count(*) from t where x = 23,那么在 MyISAM 中就不一定比 InnoDB 快了。

InnoDB 中 count(*) 语句是在执行的时候,全表扫描统计总数量,所以当数据越来越大时,语句就越来越耗时了,为什么 InnoDB 引擎不像 MyISAM 引擎一样,将总行数存储到磁盘上?这跟 InnoDB 的事务特性有关,由于多版本并发控制(MVCC)的原因,InnoDB 表“应该返回多少行”也是不确定的。

不妨用一个例子来说明一下,假设现在 t 表中有 10000 条数据,现在有三个用户同时访问的会话:

会话 A 先启动事务并查询一次表的总行数。
会话 B 启动事务,插入一行后记录后,查询表的总行数。
会话 C 先启动一个单独的语句,插入一行记录后,查询表的总行数。
会话执行流程图
在这里插入图片描述

假设从上到下是按照时间顺序执行的,同一行语句是在同一时刻执行的。可以看出在最后时刻,三个会话返回的总行数不一样。

出现不一样的结果跟 InnoDB 存储引擎有关系,在默认隔离级别可重复读的情况下,通过多版本并发控制(MVCC)来实现,每一行记录都需要判断自己是否对这个会话可见,因此在统计总数量时,InnoDB 只好把数据一行一行的读取出来判断,只有当前会话可见的才纳入统计中。所以同一时刻不同会话查询到的数量就不一样。

InnoDB 引擎在 count()语句上也做了优化,我们知道,在 InnoDB 存储引擎中是以索引组织表的方式存储数据,主键索引树上叶子节点存放在所有的数据,而普通索引树的叶子节点是主键值,所以普通索引树会比主键索引树小很多,但是数量是一样的,也就是说遍历主键索引树和普通索引树得到的结果都是一样的。MySQL 就利用了这一特性,在 InnoDB 中执行 select count() from t 语句时,MySQL 优化器会找到最小的那棵索引树来遍历,这样可能就可以减少加载次数,在一定程度上提升了 count(*)的执行效率。

实验

众所周知,mysql常用的引擎就两个:MyISAM和InnoDB。
InnoDB有很多MyISAM没有的东西,如支持事务、支持行锁、支持MVCC…
但是
MyISAM在执行count()的时候是真的快,这时候的InnoDB简直就是个弟弟。

对比如下:

实验环境

mysql版本信息
在这里插入图片描述
表数据量354w+
在这里插入图片描述

实验过程

1.不带条件的count()

将数据表引擎切换为InnoDB,花费102s左右
在这里插入图片描述
InnoDB执行不带条件count(),第一次花费28s左右,之后平均1s左右
在这里插入图片描述

将数据表引擎切换为MyISAM,花费53s左右
在这里插入图片描述

MyISAM执行不带条件count(),平均0.00027s左右
在这里插入图片描述

2.带条件的count()

MyISAM执行带条件count(),平均1.3s左右
在这里插入图片描述
在这里插入图片描述

InnoDB执行带条件count(),平均1.5s左右
在这里插入图片描述
在这里插入图片描述

实验结论

不带条件count():
MyISAM执行时间短到可以忽略不计
InnoDB第一次执行count()需要大概28s,之后每次平均在1s左右。

原因:
Myisam内置了一个计数器,直接将表的总行数存储在磁盘上
InnoDB第一次需要全表扫描统计总行数,所以很慢。

带条件count():
MyISAM和InnoDB执行时间差不多。

实验所用命令:

//统计查询 SELECT COUNT(*) AS tp_count FROM cmf_users_loginrecord where addtime between 1540111489 and 1543653889;

//查询表结构 show create table cmf_users_loginrecord;

//查询profiles是否开启 show variables;

//开启profiles set profiling = 1;

//查询sql执行时间 show profiles;

//切换表引擎 ALTER TABLE cmf_users_loginrecord ENGINE=InnoDB;

为什么 InnoDB 不跟 MyISAM 一样,也把数字存起来呢?

这是因为即使是在同一个时刻的多个查询,由于多版本并发控制(MVCC)的原因,InnoDB表“应该返回多少行”也是不确定的。 这和
InnoDB 的事务设计有关系,可重复读是它默认的隔离级别,在代码上就是通过多版本并发控制,也就是 MVCC
来实现的。每一行记录都要判断自己是否对这个会话可见,因此对于count(*) 请求来说,InnoDB
只好把数据一行一行地读出依次判断,可见的行才能够用于计算“基于这个查询”的表的总行数。
https://www.jianshu.com/p/ec52c5b72582

PS:
如何查看sql执行时间
1 show profiles;
2 show variables;查看profiling 是否是on状态;
3 如果是off,则 set profiling = 1;
4 执行自己的sql语句;
5 show profiles;就可以查到sql语句的执行时间;

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值