引言

某日,小编去面试(纯属瞎编),有了如下对话

面试官:"懂mysql吧,知道CPU在读硬盘上数据的时候,是怎么解决CPU和硬盘速度不一致问题么?"我:"懂啊,mysql先把数据页加载到内存里,然后读内存中的数据啊!"面试官:"你们用的是哪个存储引擎?"我:"嗯,innodb,因为需要用事务功能!"面试官:"嗯,好。那既然需要把数据页加载到内存里,这里必然涉及到LRU算法,当这块区域满了后,将一些不常用的数据页淘汰掉,innodb具体怎么做的?"我尴尬的笑了笑,回答道:"我只知道redis中LRU怎么做的..balabala"面试官:"停,我只想知道innodb怎么做的?"我:"我还是回去等通知吧~"

接下来回去

于是就有了本文诞生

正文

什么是BufferPool

Innodb为了解决磁盘上磁盘速度和CPU速度不一致的问题,在操作磁盘上的数据时,先将数据加载至内存中,在内存中对数据页进行操作。

Mysql在启动的时候,会向内存申请一块连续的空间,这块空间名为Bufffer Pool,也就是缓冲池,默认情况下Buffer Pool只有128M。

那缓冲池长什么样的呢,如下图所示

图片出自《Mysql运维内参》

如图所示,有三部分组成:

  • ctl: 俗称控制体,里头有一个指针指向缓存页,还有一个成员变量存储着所谓的一些所谓的控制信息,例如该页所属的表空间编号、页号
  • page:缓存页,就是磁盘上的页加载进Bufffer Pool后的结构体
  • 碎片:每个控制体都有一个缓存页。最后内存中会有一点点的空间不足以容纳一对控制体和缓存页,于是碎片就诞生的!

这个控制体ctl在mysql源码中长这样的

struct buf_block_t{//省略buf_page_t  page;byte*       frame;//省略
}
复制代码

嗯,懂C语言的自然知道,frame是一个指针啦,指向缓存页。

而page存储的就是该页所属的表空间编号、页号等。

在BufferPool中有三大链表,需要重点关注,它们存储的元素都是buf_page_t。

比如,我总要知道那些页是可以用,是空闲的吧。

OK,这些信息在free链表中维护。

再比如,CPU肯定是不会去修改磁盘上的数据。那么,CPU修改了BufferPool中的数据后,Innodb总要知道要把哪一块信息刷到磁盘上吧。

OK,这些信息在flush链表中维护。

最后,当free链表里没多余的空闲页啦,innodb要淘汰一些缓存页啦。怎么淘汰?

这还用问,一定是淘汰最近最少使用的缓存页啊。

怎么知道这些页是最近最少使用的呢?

嗯,那就是要借助传说中的LRU链表啦。

简单的LRU

我们先来说一个简单的LRU算法。LRU嘛,全称吧啦吧啦…英文名忘了。反正就是一个淘汰最近最少使用的算法。

然后就去百度了一下,我发现百度是这么说的

最常见的实现是使用一个链表保存缓存数据,详细算法实现如下:

  • 1. 新数据插入到链表头部;
  • 2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
  • 3. 当链表满的时候,将链表尾部的数据丢弃。

嗯,完美!很完美!反正innodb中不可能这样设计~

那么为什么不能这么设计呢?

原因一

假设有一张表叫yan_ge_hao_shuai,(请将表名多看几遍),回到正题,这张表什么索引都木有,有着几千万数据,反正就是很多很多数据页。然后,执行下面的语句

select * from yan_ge_hao_shuai
复制代码

因为没有任何索引嘛,那就进行全表扫描了。那么按照上面说的算法,这些数据页也会被全部塞入LRU链表,并且通通加载到BufferPool中,从而迅速清空其他查询语句留下来的高频的数据页。那么此时,你的BufferPool里全是低频的数据页,就会发现缓存命中率大大滴降低了。

于是你就会觉得:"我勒个去,设计这个Innodb的人,怕不是脑袋有问题…(以下省略一万字)"

原因二

这里先说以下innodb的预读机制,是这样子滴!这个预读细说起来可以分为线性预读和随机预读。借一张姜承尧大大的图,innodb的表逻辑结构如下图所示

从 InnoDB存储引擎的逻辑存储结构看,所有数据都被逻辑地存放在一个空间中,称之为表空间( tablespace)。表空间又由段(segment)、区( extent)、页(page)组成。页在一些文档中有时也称为块( block), InnoDB存储引擎的逻辑存储结构大致如图所示。

其实借这张图,我只想说一件事。数据页(page)是放在区(extent)里的。

那么

  • 线性预读:当一个区中有连续56个页面(56为默认值)被加载到BufferPool中,会将这个区中的所有页面都加载到BufferPool中。其实挺合理的,毕竟一个区最多才64个页。
  • 随机预读:当一个区中随机13个页面(13为默认值)被加载到BufferPool中,会将这个区中所有页面都加载到BufferPool中。随机预读默认是关闭,由变量innodb_random_read_ahead控制。

好了,上面那一堆其实看不懂也没事。我只想说一件事,预读机制会预读一些额外的页到到BufferPool中。

那么,如果这些预读页并不是高频的页呢?

OK,如果这些页并不是高频的页,按照上面的算法,也会被加入LRU 链表,就会将链表末端一些高频的数据页给淘汰掉,从而导致命中率下降。

于是你会觉得:"唉,自己写一个都比他强…(此处略过一万字)"

Innodb的LRU

OK,为了解决上面的两个缺点。Innodb将这个链表分为两个部分,也就是所谓的old区和young区。

天啦噜,这两个区干嘛用的?

ok,young区在链表的头部,存放经常被访问的数据页,可以理解为热数据!

ok,old区在链表的尾部,存放不经常被访问的数据页,可以理解为冷数据!

这两个部分的交汇处称为midpoint,往下看!

怎么知道两个区的比例?

执行下面的命令

mysql> SHOW VARIABLES LIKE 'innodb_old_blocks_pct';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_old_blocks_pct | 37    |
+-----------------------+-------+
1 row in set (0.01 sec)
复制代码

这说明了old区的比例为37%,也就是冷数据大概占LRU链表的3/8。剩下的就是young区的热数据。

于是可以得到一张大概的LRU链表图,如下所示(图片出自网络)

ps:一般生产的机器,内存比较大。我们会把innodb_old_blocks_pct值调低,防止热数据被刷出内存。

数据何时在old区,何时进入young区?

好,数据页第一次被加载进BufferPool时在old区头部。

当这个数据页在old区,再次被访问到,会做如下判断

  • 如果这个数据页在LRU链表中old区存在的时间超过了1秒,就把它移动到young区
  • 这个存在时间由innodb_old_blocks_time控制

我们来看看innodb_old_blocks_time的值,如下所示

mysql> SHOW VARIABLES LIKE 'innodb_old_blocks_time';
+------------------------+-------+
| Variable_name          | Value |
+------------------------+-------+
| innodb_old_blocks_time | 1000  |
+------------------------+-------+
1 row in set (0.01 sec)
复制代码
那怎么解决这些缺点的?

针对原因一

也就是所谓的全表扫描导致Bufferpool中的高频数据页快速被淘汰的问题。

Innodb这么做的:

(1)扫描过程中,需要新插入的数据页,都被放到old区

(2)一个数据页会有多条记录,因此一个数据页会被访问多次

(3)由于是顺序扫描,数据页的第一次被访问和最后一次被访问的时间间隔不会超过1S,因此还是会留在old区

(4)继续扫描,之前的数据页再也不会被访问到,因此也不会被移到young区,最终很快被淘汰

针对原因二

也就是预读到的页,可能不是高频次的页。

你看,你预读到的页,是存在old区的。如果这个页后续不会被继续访问到,是会在old区逐步被淘汰的。因此不会影响young区的热数据。

监控冷热数据

执行下面命令即可

mysql> show engine innnodb status\G
……
Pages made young 0, not young 0
0.00 youngs/s, 0.00 non-youngs/s
复制代码

1、数据页从冷到热,称为young;not young就是数据在没有成为热数据情况下就被刷走的量(累计值)。

2、non-youngs/s,这个数值如果很高,一般情况下就是系统存在严重的全表扫描,自然意味着很高的物理读。(上面分析过)

3、youngs/s,如果这个值相对较高,最好增加一个innodb_old_blocks_time,降低innodb_old_blocks_pct,保护热数据。

总结

本文总结了Innodb中的LRU是如何做的,希望大家有所收获。

另外,唉,最近有一番新的感慨。

  • 代码写的好,bug少,看起来像是一个闲人
  • 注释多,代码清晰,任何人可以接手,看起来就是谁都可以替代
  • 代码写的烂,每天惊动各大领导提流程改生产代码,解决生产问题,就是公司亮眼人才
  • 代码写的烂,只有自己看得懂,就是公司不可替代的重要人才

心累,社会呀~

转载于:https://juejin.im/post/5cc17ad3f265da03914d666a

面试官:说说Innodb中LRU怎么做的?相关推荐

  1. 面试官:InnoDB中一棵B+树可以存放多少行数据?

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"书",获取 来源:r6a.cn/fUA9 InnoDB一棵B+树可以存放多少行数据?这 ...

  2. 【Redis系列】面试官:Redis中的数据已经过期,为什么还占用这内存?

    如果有面试官问Redis中的数据已经过期为什么还占用这内存? 它是因为Redis本身的过期策略和缓存淘汰机制所导致的. 说说Redis的过期策略和缓存淘汰机制 先来说说Redis的过期策略,Redis ...

  3. 面试官:Vue中组件和插件有什么区别?

    一.组件是什么 回顾一下对组件的定义: 组件就是把图形.非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式,在Vue中每一个.vue文件都可以视为一个组件 组件的优势 降低整个系统的耦合度 ...

  4. 面试官:Redis中集合数据类型的内部实现方式是什么?

    虽然已经是阳春三月,但骑着共享单车骑了这么远,还有有点冷的.我搓了搓的被冻的麻木的手,对着前台的小姐姐说:"您好,我是来面试的."小姐姐问:"您好,您叫什么名字?&quo ...

  5. 【245期】面试官:同类中两个方法加同步锁,多个线程支持同时访问这两个方法吗?...

    点击上方"Java精选",选择"设为星标" 别问别人为什么,多问自己凭什么! 下方有惊喜,留言必回,有问必答! 每天 08:15 更新文章,每天进步一点点... ...

  6. 面试官:工作中遇到难题怎么解决的?看似送分题实则”送命”

    "金九银十"跳槽季,不少职场的新人,在面对这个问题是无从下手,甚至很多职场老人也会措手不及,尴尬的说:"工作中没有什么难题吧,基本上都可以解决."如果我们这样回 ...

  7. 【性能优化】面试官:Java中的对象和数组都是在堆上分配的吗?

    写在前面 从开始学习Java的时候,我们就接触了这样一种观点:Java中的对象是在堆上创建的,对象的引用是放在栈里的,那这个观点就真的是正确的吗?如果是正确的,那么,面试官为啥会问:"Jav ...

  8. mysql 修改字段长度_面试官:InnoDB记录存储结构都不知道,你敢说你懂MySQL?

    前言 了解MySQL的人都知道,MySQL服务器上负责对表中数据的读取和写入工作的部分是存储引擎,而MySQL的存储引擎有MyISAM和InnoDB.不同的存储引擎一般是由不同的人为实现不同的特性而开 ...

  9. 面试官问我new Vue阶段做了什么?

    前言 本篇录入吊打面试官专栏,希望能祝君拿下Offer一臂之力,各位看官感兴趣可移步

最新文章

  1. 白话讲山寨SOA,少一些迷惑、多一些理解,你的程序架构SOA了吗?
  2. (0047)iOS开发之nil/Nil/NULL的区别
  3. html左右飘窗高度不一致,飘窗的最佳尺寸,你可知道?不懂的留着吧!
  4. DOS下导入导出MySQL备份
  5. build.gradle代码
  6. PS网页设计教程——30个优秀的PS网页设计教程的中文翻译教程
  7. springboot 参数校验详解
  8. 数据结构——从叶子结点到根节点的全部路径
  9. 使用LinkedHashMap的Code4ReferenceList最近使用(LRU)实现
  10. 前端学习(3125):react-hello-react之类式组件里的构造器域props
  11. 43 MM配置-采购-条件-定价过程-定义存取顺序
  12. 既然选择了远方,便只顾风雨兼程……
  13. iconfont阿里巴巴矢量图标库使用步骤
  14. 网易云信短信功能使用
  15. 中控人脸指纹考勤机怎么如何偷偷修改数据记录
  16. 【c语言】两个队列实现一个栈
  17. 前端-JS基础之运算符
  18. IOS AutoFill Extension 使用
  19. iBeacon让互联网营销进入场景时代
  20. 华清远见嵌入式学习笔记

热门文章

  1. JavaScript 之 call和apply,bind 的模拟实现
  2. C/C++中字符串与数字之间的转换
  3. 使用WebRTC搭建前端视频聊天室——数据通道篇
  4. EXPORT_SYMBOL的作用是什么
  5. 菱形开合的实现 IOS
  6. 地理信息系统控件GIS控件TatukGIS Developer Kernel 下载及介绍
  7. 其他资源记录类型及应用示例
  8. WCF步步为营(五):数据契约
  9. JDK+TOMCAT在LINUX下简单的配置
  10. Android app开发捷径,让你少去踩坑