一文读懂 InnoDB 缓冲池(buffer pool) 工做原理

缓冲池的用处

对于使用 InnoDB 做为存储引擎的表来讲,不论是用于存储用户数据的索引,仍是各类系统数据,都是以页的形式存放在表空间中的,而所谓的表空间只是 InnoDB 对文件系统上一个或几个实际文件的抽象,也就实际数听说到底仍是存储在磁盘上的。算法

磁盘的速度很慢,怎么能配得上“快如闪电”的CPU 呢?缓存

InnoDB 存储引擎在处理客户端的请求时,当须要访问某个页的数据时,就会把完整的页的数据所有加载到内存中。服务器

也就是说即便咱们只须要访问一个页的一条记录,那也须要先把整个页的数据加载到内存中。性能

缓冲池内部组成

缓冲池中默认的缓存页大小和在磁盘上默认的页大小是同样的,通常是16KB。优化

为了更好的管理这些在缓冲池中的缓存页,InnoDB为每个缓存页都建立了一些所谓的控制信息。spa

这些控制信息包括该页所属的表空间编号、页号、缓存页在缓冲池中的地址、链表节点信息、一些锁信息。操作系统

缓冲池的一些参数:
SHOW VARIABLES LIKE 'innodb_buffer_pool%';
free 链表
当最初启动MySQL服务器的时候,此时并无真实的磁盘页被缓存到缓冲池中,以后随着程序的运行,会不断的有磁盘上的页被缓存到缓冲池中。3d

从磁盘上读取一个页到缓冲池中的时候该放到哪一个缓存页的位置呢?code

思路:区分缓冲池中哪些缓存页是空闲的,哪些已经被使用了。blog

把全部空闲的缓存页对应的控制块做为节点放到一个链表中,这个链表叫做 free 链表。

flush 链表

若是咱们修改了缓冲池中某个缓存页的数据,那它就和磁盘上的页不一致了,这样的缓存页也被称为脏页(dirty page)。

最简单的作法就是每发生一次修改就当即同步到磁盘上对应的页上,可是频繁的往磁盘中写数据会严重的影响程序的性能。

因此,Innodb 建立了一个存储脏页的链表,凡是修改过的缓存页对应的控制块都会做为一个节点加入到一个链表中,在将来的某个时间点进行同步。这个链表叫作 flush 链表。

缓存不够的窘境

缓冲池对应的内存大小毕竟是有限的,若是须要缓存的页占用的内存大小超过了缓冲池大小,也就是已经没有多余的空闲缓存页的时候怎么办?

把某些旧的缓存页从缓冲池中移除,而后再把新的页放进来。

移除哪些缓存页?这就须要引入缓存淘汰机制了。

缓存淘汰机制

缓存淘汰有如下两个目的:

  • 实现淘汰
  • 使缓存命中率高

缓存淘汰机制比较经常使用的是用 LRU (Least recently used)算法。

传统LRU

LRU 的两种状况:

(1)页已经在缓冲池里,那就只作“移至”LRU头部的动做,而没有页被淘汰;

(2)页不在缓冲池里,除了作“放入”LRU头部的动做,还要作“淘汰”LRU尾部页的动做;

在 InnoDB 中,传统的 LRU 会遇到两个问题:

(1)预读失效;

(2)缓冲池污染;

什么是预读失效?

因为预读 (Read-Ahead),提早把页放入了缓冲池,但最终 MySQL 并无从页中读取数据,称为预读失效。

如何对预读失效进行优化?

要优化预读失效,思路是:

(1)让预读失败的页,停留在缓冲池 LRU 里的时间尽量短;

(2)让真正被读取的页,才挪到缓冲池 LRU 的头部;

以保证,真正被读取的热数据留在缓冲池里的时间尽量长。

具体方法是:

(1)将LRU分为两个部分:

  • new 区(new sublist)
  • old 区(old sublist)

(2)两个区首尾相连,即:new 区的尾(tail)链接着 old 区的头(head);

(3)新页(例如被预读的页)加入缓冲池时,只加入到 old 区头部:
若是数据真正被读取(预读成功),才会加入到 new 区的头部
若是数据没有被读取,则会比 new 区里的“热数据页”更早被淘汰出缓冲池

改进版缓冲池LRU可以很好的解决“预读失败”的问题。

查看系统变量 innodb_old_blocks_pct 的值来肯定old区域在LRU链表中所占的比例
SHOW VARIABLES LIKE 'innodb_old_blocks_pct';

什么是 MySQL 缓冲池污染?
当某一个SQL语句,要批量扫描大量数据时,可能致使把缓冲池的全部页都替换出去,致使大量热数据被换出,MySQL性能急剧降低,这种状况叫缓冲池污染。

例如,有一个数据量较大的用户表,当执行
select * from user where name like "%test%";

要优化缓冲池污染,思路是:

(1)不让批量扫描的大量数据进入到 new 区;

(2)让真正被读取的页,才挪到缓冲池 LRU 的头部;

具体实现:
加入了一个“old 区停留时间”的机制:
在 old 区域的缓存页进行第一次访问时就在它对应的控制块中记录下来这个访问时间,若是后续再次访问的时间与第一次访问的时间在某个时间间隔内(即该缓存页在 old 区的存在时间在某个时间间隔内),那么该页面就不会被从old 区移动到 new 区的头部。

上述的全表扫描执行:

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

(2) 一个数据页会有多条记录,所以一个数据页会被访问屡次

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

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

这个间隔时间是由系统变量 innodb_old_blocks_time 控制的。

SHOW VARIABLES LIKE 'innodb_old_blocks_time';

配置缓冲池时的注意事项
innodb_buffer_pool_size
innodb_buffer_pool_chunk_size × innodb_buffer_pool_instances 的倍数(这主要是想保证每个 缓冲池 实例中包含的 chunk 数量相同)。

查看Buffer Pool的状态信息

SHOW ENGINE INNODB STATUS\G

一些参数以下:

Total memory allocated :表明 Buffer Pool 向操做系统申请的连续内存空间大小,包括所有控制块、缓存页、以及碎片的大小。

Buffer pool size:表明该 Buffer Pool 能够容纳多少缓存页,单位是页

Free buffers:表明当前 Buffer Pool 还有多少空闲缓存页,也就是 free 链表中还有多少个节点。

Database pages:表明 LRU 链表中的页的数量,包含 new 和 old 两个区域的节点数量。

Old database pages:表明 LRU 链表 old 区域的节点数量。

Modified db pages:表明脏页数量,也就是 flush 链表中节点的数量。

总结

一、磁盘太慢,用内存做为缓存颇有必要。

二、缓冲池本质上是InnoDB向操做系统申请的一段连续的内存空间,能够经过innodb_buffer_pool_size 来调整它的大小。

三、InnoDB 使用了许多链表来管理缓冲池。

四、缓冲池的常见管理算法是 LRU

五、InnoDB 对普通 LRU 进行了优化:分为 new 区和 old 区,加入“停留时间”机制。

更多好文,关注公众号获取

file

相关文章
相关标签/搜索