面试官:来,说说你对MySQL InnoDB Buffer Pool的理解
说个比较经典的面试场景吧,不但很经典,还很有喜感。
面试官:“可以说说,为什么Redis比MySQL快吗?”
候选人听了之后略带不屑,说:“这个很简单啊,因为Redis中的数据都是存储在内存中的,而MySQL中的数据都是存储到磁盘上的,内存当然要比磁盘快啊。”
面试官:“但MySQL InnoDB中不是也有一个大的Buffer Pool(缓冲池)吗?它不也是在内存中吗?”
候选人听了有些懵逼,顿了顿说:“不好意思,我对您说的这个Buffer Pool不太了解。”
面试官听了,故作失望地摇了摇头,心里则乐开了花。
现在MySQL的高频面试题,基本上都集中在锁、索引、事务、分库分表、SQL优化等相关方向上。
但Buffer Pool这块的知识点,是非常能能体现出一个工程师在数据库方向技术深度的,本文我们就来详细地聊一聊。
InnoDB存储引擎结构
我们以MySQL 8.4版本为准,InnoDB存储引擎架构如下图所示:
从图中我们可以看到,整体分为内存结构和磁盘结构两个部分,其中内存结构包括:Buffer Pool(缓冲池)、Change Buffer(写缓冲区)、Adaptive Hash Index(自适应Hash索引)和Log Buffer(日志缓冲区)。
其中,Change Buffer 和 Adaptive Hash Index 也都是存在于 Buffer Pool 中的。
一般情况下,我们会将数据库服务器80%的物理内存分配给Buffer Pool进行使用。
初识Buffer Pool
简而言之,Buffer Pool其实就是一块内存区域,InnoDB会将频繁访问的表记录数据和索引数据放到Buffer Pool中,旨在以此减少磁盘的IO操作,提升数据库的读写性能。
Buffer Pool也是以Page(默认16k)作为最小IO单元的。通常情况下,一个Page可以容纳多行数据记录,各Page间使用链表进行组织串联,并通过改良后的LRU(最近最少使用)算法来执行内存淘汰策略。
当数据库读取一条数据记录时,会先将从磁盘读取到该数据记录所对应的Page,并将其放到Buffer Pool中,然后再返回结果。如果以后读取的数据记录也在该Page中,只要其没有被淘汰,则直接从Buffer Pool中返回结果即可,不需要再从磁盘中读取了。
当数据库修改一条数据记录时,会先修改Buffer Pool中该数据记录所对应的Page,再通过Master Thread按照一定的频率将该Dirty Page刷到磁盘上。
Dirty Page,指的是内存(Buffer Pool)和磁盘数据不一致的Page。
如下图所示,Buffer Pool不仅仅存储上文中提到的Data Page、Index Page、Change Buffer 和 Adaptive Hash Index,还包括 Undo Page 和 Lock Info(锁信息)。
Change Buffer :如果一个非唯一索引并不存在于Buffer Pool的Index Page中,若对其执行写操作(insert、delete、update),会产生成本较高的磁盘随机IO。
此时,可以将写操作缓存在Change Buffer中,然后再以该Index Page被访问、Master Thread定期执行、数据库关闭作为触发点,将多个写操作合并为一个,并一次性写入到磁盘中,以减少磁盘IO次数的方式来提升写入性能。
如下图所示:
Adaptive Hash Index:InnoDB会为被频繁访问的Index Page创建一个Hash Index来提升性能,其属于自优化(Adaptive )行为。
Buffer Pool 的 LRU 算法
Buffer Pool中有一个free链表,里面保存着未被使用的Page。如果free链表中的Page已全部分配完毕,此时再要申请空间,则需要根据LRU(最近最少使用)算法来淘汰正在使用中的Page。
我们在上文中说过,InnoDB采用改良后的LRU算法来执行Buffer Pool的内存淘汰策略,整体是通过链表数据结构来进行管理的。
如上图所示,整个链表被分为New Sublist和Old Sublist两个部分,前者占整个链表长度的5/8,存储的是最近被频繁访问的Page;后者只占3/8,存储的是最近访问次数较低的Page,这些Page会有被淘汰的可能。
当一个新的Page被写入到Buffer Pool中,InnoDB会将其放至上图中Midpoint的位置上,也就是Old Sublist的Head位置。
一个Page会由于两种情况被加载到Buffer Pool中,一个是当用户执行SQL语句进行访问,另一个则是InnoDB自动执行预读访问。
预读操作,顾名思义,在SQL查询操作中,InnoDB会提前读取“后续很有可能被访问到”的Data Page写入到Buffer Pool中,以减少磁盘IO次数的方式来提升后续查询性能。
在数据库运行过程中,如果Buffer Pool的Old Sublist中有Page被用户执行的SQL语句访问到,那该Page会被移动到New Sublist的Head位置,使其“返老还童”。
那些最近未被访问的Page会逐渐地向链表的Tail方向移动,以表示其“逐渐老化”,并且随着新的Page被写入到Buffer Pool中Old Sublist的Head位置,Old Sublist中的Page也会“逐渐老化”。
最终,一个长时间未被使用的Page到达了Old Sublist中的Tail位置,被执行了淘汰操作。
Buffer Pool VS Redis
知乎上有个帖子是这样问的,“既然有了InnoDB Buffer Pool,为什么还需要Redis?”
哈哈哈,“既生瑜,何生亮”的既视感有没有?
乍一看,确实是这么一回事,毕竟Buffer Pool也是内存操作,而且也有自己的LRU淘汰机制,跟Redis的基本面确实很像。
难道,Redis真的成为那个“多余的人”了吗?当然不是!
由于使用了InnoDB存储引擎的MySQL,是一个具备ACID事务特性的关系型数据库,在其内部实现上也做了大量的机制来保证其事务特性,这些机制对MySQL InnoDB的性能是影响较大的。
而Buffer Pool正是通过内存访问的方式来大幅降低磁盘访问频率,以此缓解这些机制对MySQL InnoDB的性能影响,且支持更高的并发访问度。
而使用Redis的目标则更加简单直接,旨在通过中心化内存数据库的方式来提升业务系统的性能,使其可以扛住更多的用户请求。
这里面存在两个不同点:
(1)目标作用域不同,一个作用于数据库底层,另一个作用于业务系统,前者只能算作“曲线救国”。
因为在亿级别数据量的大表中,数据库底层优化得再好,也架不住一条全表扫描的二逼SQL。
(2)目标范围不同,前者兼顾多维度访问的相对均衡,后者追求Key Value单一场景的唯快不破。
嗯,这些就是我对这个问题的理解。
- 点赞
- 收藏
- 关注作者
评论(0)