一文详解Innodb存储引擎原理

刘军连 https://m-mip.39.net/baidianfeng/mipso_4147810.html
大纲

本文主要讲解InnoDB存储引擎的执?原理,在此引入几个问题:

数据页和缓存页是什么?如何知道哪些缓存页是空闲的,哪些缓存页是可被清除的?

mysql预读机制了解过吗,什么情况下会触发它?mysql是为了应对什么样的场景才设计预读机制?

类?redis在内存中也存在冷热数据共存的场景,如何考虑利?lru链表解决预读机制的思想、来对redis缓存的设计进?优化?

下面的图描述了整个执行过程:

磁盘数据如何加载到mysql中?

?般我们要更新?条数据,数据?开始肯定是存放在磁盘中的,用到时才会被加载到mysql,存放的数据在逻辑概念上我们称为表,物理层?上在磁盘中是按数据页形式存放的,那么加载到mysql中的就称为缓存页。每个缓存页都有对应的?份描述信息,存放了缓存?的?些元数据相关的?些信息,通过描述信息可以快速定位到缓存页,最开始描述信息指向的缓存?当然都是空闲没有数据的,从磁盘加载数据页信息。

mysql在磁盘以一页16K的数据按页存储,所以在初始化bufferpool的时候,同时也将内存按页划分,作为缓存页。

BufferPool是在MySQL启动的时候,向操作系统申请的一片连续的内存空间,默认配置下BufferPool只有MB。

可以通过调整innodb_buffer_pool_size参数来设置BufferPool的大小,一般建议设置成可用物理内存的60%~80%

当有了BufferPool后,它的作用:

当读取数据时,如果数据存在于BufferPool中,客户端就会直接读取BufferPool中的数据,否则再去磁盘中读取。

当修改数据时,首先是修改BufferPool中数据所在的页,然后将其页设置为脏页,最后由后台线程将脏页写入到磁盘

如何快速找到空闲缓存页

既然现在我们已经知道磁盘中的数据页是加载到bufferpool缓冲池中的,那么我们怎么样才能知道哪些缓存页是空闲的?哪些缓存页是没有被加载过数据页信息的呢?

为了能够快速找到空闲的缓存页,可以使用链表结构,将空闲缓存页的「描述信息」作为链表的节点,这个链表称为Free链表(空闲链表)。

有了Free链表后,每当需要从磁盘中加载一个页到BufferPool中时,就从Free链表中取一个空闲的缓存页,并且把该缓存页对应的控制块的信息填上,然后把该缓存页对应的控制块从Free链表中移除。

此时数据页被加载到缓存页了,缓存页中已经有数据了,相关的变动信息肯定也要回写到描述信息中,并且现在因为缓存页已经有数据,就不能再待在free链表中了,就需要将该缓存页对应的描述信息节点从free链表给摘掉,转移到了lru链表中,如下图所示

lru链表实现的目的就是为让哪些被访问的缓存页能够尽量排到靠前位置,那么此时如果此时内存不够需要淘汰掉?些缓存页时,此时就可以到lru链表尾部,将哪些最近最少被访问的尾部节点给刷盘释放缓存页腾出内存来。

简单的LRU算法的实现思路是这样的:

当访问的页在BufferPool里,就直接把该页对应的LRU链表节点移动到链表的头部。

当访问的页不在BufferPool里,除了要把页放入到LRU链表的头部,还要淘汰LRU链表末尾的节点。

简单的LRU算法并没有被MySQL使用,因为简单的LRU算法无法避免下面这两个问题:

预读失效;

BufferPool污染;

预读失效

先来说说MySQL的预读机制。程序是有空间局部性的,靠近当前被访问数据的数据,在未来很大概率会被访问到。

所以,MySQL在加载数据页时,会提前把它相邻的数据页一并加载进来,目的是为了减少磁盘IO。

但是可能这些被提前加载进来的数据页,并没有被访问,相当于这个预读是白做了,这个就是预读失效。

如果使用简单的LRU算法,就会把预读页放到LRU链表头部,而当BufferPool空间不够的时候,还需要把末尾的页淘汰掉。

如果这些预读页如果一直不会被访问到,就会出现一个很奇怪的问题,不会被访问的预读页却占用了LRU链表前排的位置,而末尾淘汰的页,可能是频繁访问的页,这样就大大降低了缓存命中率。

要避免预读失效带来影响,最好就是让预读的页停留在BufferPool里的时间要尽可能的短,让真正被访问的页才移动到LRU链表的头部,从而保证真正被读取的热数据留在BufferPool里的时间尽可能长。

那到底怎么才能避免呢

优化后的lru链表主要引?了冷热数据分离的思想解决了mysql预读机制带来的问题。把lru链表分为热数据区和冷数据区,热数据区主要存放那些访问频率?的缓存?,冷数据区存放访问频率较低的缓存页;从磁盘加载数据到lru链表时,?先会将加载到的缓存页直接先放到冷数据链的表头,如果ms(默认,可配置)后冷数据的缓存页又被访问了,此时就认为这些ms之后被访问的缓存页,在不久的未来可能还会被访问,可以认为它们是热数据了,就会把这些缓存页从冷数据区的链表给移动到热数据区链表的表头,通过该步骤可以将热数据从冷数据堆中给巧妙的分离出来。

脏页什么时候会被刷入磁盘?

引入了BufferPool后,当修改数据时,首先是修改BufferPool中数据所在的页,然后将其页设置为脏页,但是磁盘中还是原数据。

因此,脏页需要被刷入磁盘,保证缓存和磁盘数据一致,但是若每次修改数据都刷入磁盘,则性能会很差,因此一般都会在一定时机进行批量刷盘。

InnoDB的更新操作采用的是WriteAheadLog策略,即先写日志,再写入磁盘,通过redolog日志让MySQL拥有了崩溃恢复能力

为此,innodb设计了flush链表,在缓冲池中被更新过数据的缓存页,这些缓存?的描述信息都会被添加到flush链表中。

下面几种情况会触发脏页的刷新:

当redolog日志满了的情况下,会主动触发脏页刷新到磁盘;

BufferPool空间不足时,需要将一部分数据页淘汰掉,如果淘汰的是脏页,需要先将脏页同步到磁盘;

MySQL认为空闲时,后台线程会定期将适量的脏页刷入到磁盘;

MySQL正常关闭之前,会把所有的脏页刷入到磁盘;




转载请注明:http://www.aierlanlan.com/rzfs/8879.html