1. 基本概念
InnoDB的数据最终会落到磁盘上,内存主要用于热数据的缓存。InnoDB在内存中的模块主要有:
Buffer Pool
Page
Free List
Flush List
LRU List
page hash表
下面分别介绍这几种数据结构以及MySQL是怎么通过这些数据结构来管理内存的。
2. 数据结构介绍
Buffer Pool
InnoDB内存页的缓冲池,其中的内存页实际是被各种链表给管理起来的。Buffer Pool分为多个Instance,每个Instance的数据是独立的,可以并发的进行读写。
Page
MySQL中内存管理的最小单位。
Free List
其上的节点都是未被使用的节点,如果需要从数据库中分配新的数据页,直接从上获取即可。InnoDB需要保证Free List有足够的节点,提供给用户线程用,否则需要从Flush List或者LRU List淘汰一定的节点。InnoDB初始化后,Buffer Chunks中的所有数据页都被加入到Free List,表示所有节点都可用。
Flush List
保存所有脏页,也就是被修改但还没有被刷到磁盘上的数据页。
LRU List
保存从磁盘中读取到的数据页,并用一种优化后的LRU算法策略进行淘汰,优化后的算法被称为midpoint insertion strategy。这个算法在LRU列表中加入了midpoint的位置。新读取的页,并不直接放到LRU列表的首部,而是放入到LRU列表的midpoint位置,midpoint默认为5/8的位置。midpoint之前的链表被称为young list, midpoint之后的链表被称为old list。在old list的page在一定时间内被第二次访问时才会被移到young list,当需要淘汰数据时,从old list的尾部进行淘汰。
为什么需要这种优化策略呢?
对于普通的LRU算法,如果有索引或表扫描操作,就会导致大量的页被加入到内存cache中,但是这些页可能接下来并不会被频繁使用到,热点的页被这些页逐出内存,导致性能的下降。使用midpoint insertion strategy,这些扫描产生的大量的页,只会被加入到old列表,从而不会影响到热点数据所在的new列表。通常old列表都比较短(取决于你认为数据库的热点数据的比例),这些页就会很快被逐出内存。
page hash表
通过它,使用space_id和page_no就能快速找到已经被读入内存的数据页,而不用线性遍历LRU List去查找。
3. 内存管理总结
我们从一个数据页的访问流程来看下上面介绍的这些内存模块是怎么相互配合来实现高效的内存页管理的。
首先根据读取的数据页的page no和space id,找到对应的buffer pool instance
根据page hash表查找对应的页是否已经在内存中,如果已经在内存中,则调整该内存页在LRU list中的位置,并返回,否则从磁盘中读取对应的数据页
在free list中找到一个空闲页面来缓存读取到的数据页,如果找到了,将这个页面加入到LRU List中并成功返回,否则代表free list中没有空闲的数据页了
尝试从LRU List的末尾找到一个可以被淘汰的页面,加入到free list中,然后从free list中找到一个空闲的页面来存储当前的数据页
如果在第4步没有找到可以被淘汰的页面,则要进行单页刷脏操作,刷脏后,将释放的内存块加入到free list中
4. 脏页刷新
如果redo log的大小达到上限或缓冲池的内存大小到达上限,就会触发脏页刷新逻辑。MySQL也有后台异步线程来定时的刷新一定比例的脏页。




