1. Mysql架构
  2. sql执行过程
  3. InnoDB总体架构解析
  4. B+树详细解析

题记

上面两节,梳理了Mysql的架构以及Sql的执行流程,这节开始探索Mysql存储引擎,InnoDb的内部结构。下雨还能一直下,总该会有晴天,比如就在今天。

1.整体架构图




innodb_buffer_pool_instances在哪里设置 innodb buffer pool size_Powered by 金山文档


2.内部结构

内存结构主要包括Buffer Pool、Change Buffer、Adaptive Hash Index和Log Buffer四大组件。

2.1 Buffer Pool

Buffer Pool缓存的是页面信息,包括数据页、索引页。

Buffer Pool 默认大小是128M (134217728 字节),可以调整。

问题:内存的缓冲池写满了怎么办?

InnoDB用LRU算法来管理缓冲池,经过淘汰的数据就是热点数据。啥玩意,LRU算法?不要着急,下边让我们慢慢探索。

查看系统变量:

SHOW VARIABLES like '%innodb_buffer_pool%';


innodb_buffer_pool_instances在哪里设置 innodb buffer pool size_数据_02


查看服务器状态,里面有很多跟 Buffer Pool相关的信息:

SHOW status like '%innodb_buffer_pool%';


innodb_buffer_pool_instances在哪里设置 innodb buffer pool size_mysql_03


这些参数都可以在官网查到详细的含义可参考下述链接了解

https://dev.mysgl.com/doc/refman/5.7/en/server-system-variables.html

1. Innodb怎么存储的呢

首先,InnoDB的数据页并不是都是在访问的时候才缓存到 buffer pool的。

InnoDB 有一个预读机制 (read ahead)。也就是说,设计者认为访问某个 pag的数据的时候,相邻的一些 page 可能会很快被访问到,所以先把这些 page 放到 bufepool中缓存起来。

预读的机制又分为两种类型:

第一种叫线性预读(异步的)(Linear read-ahead)为了便于管理,InnoDB 中把64 个相邻的 page 叫做一个extent (区)。如果顺序地访问了一个extent的 56(可根据参数控制)个 page,这个时候InnoDB 就会把下一个 extent (区) 缓存到buffer pool中。

顺序访问多少个 page 才缓存下一个 extent,由一个参数控制:

show variables like 'innodb_read_ahead_threshold';


innodb_buffer_pool_instances在哪里设置 innodb buffer pool size_mysql_04


第二种叫做随机预读 (Random read-ahead) ,如果buffer pool 已经缓存了同个extent(区)的数据页的个数超过13 时,就会把这个extent 剩余的所有 page 全部缓存到 buffer pool。

但是随机预读的功能默认是不启用的,由一个参数控制:

show variables like 'innodb_random_read_ahead';


innodb_buffer_pool_instances在哪里设置 innodb buffer pool size_Powered by 金山文档_05


线性预读或者异步预读,能够把可能即将用到的数据提前加载到 buffer pool,能提升I/0的性能,所以是一种非常有用的机制。但是预读肯定也会带来一些副作用,就是导致占用的内存空间更多,剩余的空闲页更少。如果说 buffer pool size 不是很大,而预读的数据很多,很有可能那些真正的需要被缓存的热点数据被预读的数据挤出 buffer pool,淘汰掉了。下次访问的时候又要先去磁盘。怎么办呢,怎么让这些真正的热点数据不受到预读的数据的影响呢?不能不说前人思维的伟大。

2. Innodb存储详情

我们可以把buffer pool缓存的数据划分为冷热区,靠近head 的叫做 new sublist用来放热数据(我们把它叫做热区)。靠近 tail 的叫做old sublist,用来放冷数据(我们把它叫做冷区)。中间的分割线叫做 midpoint。也就是对 buffer pool 做个冷热分离。如下图:


innodb_buffer_pool_instances在哪里设置 innodb buffer pool size_数据_06


所有新数据加入到 buffer pool的时候,一律先放到冷数据区的 head,不管是预读的,还是普通的读操作。所以如果有一些预读的数据没有被用到,会在 oldsublist (冷区)直接被淘汰。

放到 LRU List 以后,如果再次被访问,都把它移动到热区的 head,如果热区的数据长时间没有被访问,会被先移动到冷区的 head 部,最后慢慢在tail 被淘汰。如下图


innodb_buffer_pool_instances在哪里设置 innodb buffer pool size_数据区_07


这时候存储就变成了这样:

新读到的页,虽然是最新访问的页,但并不是直接插入到LRU列表的首部,而是插入LRU列表的midpoint位置。这个算法称之为midpoint insertion stategy。默认配置插入到列表长度的5/8处。midpoint由参数innodb_old_blocks_pct控制。

midpoint之前的列表称之为new列表,之后的列表称之为old列表。可以简单的将new列表中的页理解为最为活跃的热点数据。

同时InnoDB存储引擎还引入了innodb_old_blocks_time来表示页读取到mid位置之后需要等待多久才会被加入到LRU列表的热端。可以通过设置该参数保证热点数据不轻易被刷出。该参数默认是1s。

那并发时候怎么办呢?

3.并发处理

为了避免并发的问题,对于LRU链表的操作是要加锁的。也就是说每一次链表的移动,都会带来资源的竞争和等待。从这个角度来说,如果要进一步提升InnoDB LRU的效率,就要尽量地减少LRU链表的移动比如,把热区一个非常靠近head的 page移动到head,有没有这个必要呢?

所以InnoDB对于new 区还有一个特殊的优化:

如果一个缓存页处于热数据区域,且在热数据区域的前1/4 区域(注意是热数据区域的 1/4,不是整个链表的 1/4),那么当访问这个缓存页的时候,就不用把它移动到热数据区域的头部;

如果缓存页处于热区的后 3/4 区域,那么当访问这个缓存页的时候,会把它移动到热区的头部。

2.2 LRU算法

LRU是Least Recently Used的缩写,即最近最少使用页面置换算法,由于无法预测各页面(计算机是用页维度来进行加载数据)将来的使用情况,只能利用“最近的过去”作为“最近的将来”的近似,因此,LRU算法就是将最近最久未使用的页面予以淘汰。

详细的该算法讲解也是站在大佬肩膀上仰望: