MySQL InnoDB存储引擎体系架构 —— 内存管理

我们都知道,InnoDB引擎是基于磁盘存储的,但由于物理硬盘访问速度与内存访问速度存在着巨大的鸿沟,InnoDB常用缓冲池技术来提高数据库的性能。

与常用的缓存思想类似,在数据库中读取页的操作,首先将磁盘读到的页放在缓冲池当中,下一次再读相同页时,先检查该页是否在缓冲池当中。若在缓冲池中,则该页在缓冲池中被命中,直接读取该页,否则读取磁盘中的页。可见,缓冲池的大小非常影响MySQL的性能。缓冲池在MySQL用innodb_buffer_pool_size变量表示,可以在my.cnf文件中设置,查看方式如下图,可见,缓冲池的大小是134217728/1024/1024=128M(当然在生产环境下128M太小)。

show variables like 'innodb_buffer_pool_size'\G;

在数据库中修改页的操作,首先修改缓冲池中页的数据,然后以一定频率异步地将缓冲池页刷新到磁盘上,这种技术叫Checkpoint机制,这样的目的也是为了提高MySQL整体性能。

缓冲池是一块很大的内存区域,其中存放各种类型的页,默认每页的大小是16K,让我们来看一下缓冲池中数据页的类型:索引页,数据页,redo页,插入缓冲,自适应哈希索引,锁信息,数据字典等,那么InnoDB是如何管理内存的呢?

一、页的管理

1、LRU List

LRU,Latest Recent Used,最近最少使用算法。缓存池可以被认为一条长LRU链表,该链表又分为2个子链表,一个子链表存放old pages(里面存放的是长时间未被访问的数据页),另一个子链接存放new pages(里面存放的是最近被访问的数据页面)。

与传统的LRU算法不同,innoDB对LRU算法进行优化,插入的数据不在LRU List的首部,在innoDB中引入了一个midpoint的概念,将新的数据插入到LRU List的midpoint位置处。我们可以通过命令查看midpoint的值

show variables like 'innodb_old_blocks_pct'\G;

可以看到midpoint默认值是37,midpoint之前是newPage占37%,midpoint之后是oldPage,可以通过命令调整midpoint'的值

set global innodb_old_blocks_pct=38

思考:innodb为什么要设置midpoint而不用传统的LRU算法呢

答:这是因为若直接将读取的页放在LRU列表的首部,那么某些SQL操作可能会使缓冲池中的页被刷新出,从而影响缓冲的命中率。常见的操作如需要访问表中的很多页,也许这些页并不是热点数据,如果放在LRU列表首部,但这些页有可能会将热点数据刷出缓冲池。引入midpoint,将新查的数据存储在midpont位置中,midpoint之前的仍为最热数据。

2、Free List

当MySQL刚启动时,LRU List是空的,这时的页都存放在Free List中。当需要从缓冲池中分页时,首先从Free List中查找是否有空闲页,如果有则从FreeList中移除,放在LRU List中。我们可以根据以下命令查看LRU List和Free List的数据

show engine innodb status\G; 

其中有几个重要的参数,我已经标红,在下面一一解释:

  • Buffer pool size:缓冲池中页的个数,每页默认大小16k,则缓冲池的大小是8192*16/1024=128M。
  • Free buffers:Free List页的个数
  • Database pages:LRU List页的个数
  • Modified db pages:脏页的个数,由于在进行update操作时首先会修改缓冲池中的数据,在定时异步的将缓冲池的数据刷新到磁盘中(checkpoint技术),所以缓冲池的数据与磁盘的数据会产生不一致,称为脏页。
  • LRU len:LRU List的长度。

3、Flush List

在LRU中的页被修改后,该页称为脏页,即缓冲池中的页和磁盘上的页产生了不一致,而Flush List中的页即为脏页列表。注意:脏页既存在于LRU List中,也存在Flush List中,LRU List用来管理缓冲池中可用的页,Flush List用来管理将脏页刷新到磁盘上,二者互不影响。下面我用一个例子来给大家验证Flush List和Modified db pages;

有一张user表存有如下数据:

这时我们查看Modified db pages的值为0:

当我们update的时候,我执行如下命令,修改数据并查看脏页的值,之所以两条命令一起执行,是为了可以看到脏页的值的变化,如果分成两次执行,有可能checkpoint机制已将修改的数据刷新到磁盘中而观测不到脏页的值。

update user set id=5 where id=4;show engine innodb status\G;

我们可以看到Modified db pages的值确实变化了,表明又脏页产生。

二、插入缓冲(Insert Buffer)

听到这个名字,可能会让人认为insert? buffer是缓冲池中的一部分,其实不是,insert buffer和数据页一样,也是物理页中的一个组成部分。

在InnoDB中,主键是行的唯一标识,如果我们的主键是auto_increment的话,插入顺序是有序的,一般情况下不需要读取另一页的数据,所以插入速度非常快,如下表:

但不可能每张表都只有一个聚集索引,大多情况下,每张表会有非聚集索引。比如用户按照b字段查询,而且b字段不是唯一的,在insert时,主键a还是按照有序存放,但非聚集索引b的叶子节点插入的不一定是有序了。如下表:

InnoDB设计的Insert Buffer,对非聚集索引的插入和更新操作,不是每次一都直接插入索引页(index page)中,而是先判断插入的非聚集索引页是否在缓冲池中存在,若在则直接插入,若不在,则先放入到一个Insert Buffer对象中。然后再以一定频率执行Insert Buffer和index page的合并操作,这时候能将多个insert合并到一个操作中,大大提高了非聚集索引插入的性能。我理解的Insert Buffer的操作如下图所示:对于insert操作,首先进入insert buffer中,然后以一定频率将索引merge到index page中,checkpoint定时将数据刷新到磁盘中。

然而,InnoDB使用Insert Buffer需要同时满足一下两个条件:

  • 索引是非聚集索引
  • 索引不是唯一的

如果索引是唯一的,在每次插入的时候先会判断索引值是否已经存在,这样会随机读取index page,从而导致Insert Buffer失去了意义。

通过命令可以查看到insert buffer的信息:

show engine innodb status\G;
  • size:已经和index page合并并记录页的数量;
  • free list:空闲列表的长度;
  • seg size:当前insert buffer的大小,2*16k=32k。

对insert buffer的形象理解(摘自网络)

我们去图书馆还书,对应图书馆来说,他是做了insert(增加)操作,管理员在1小时内接受了100本书,这时候他有2种做法把还回来的书归位到书架上

1)每还回来一本书,根据这本书的编码(书柜区-排-号)把书送回架上

2)暂时不做归位操作,先放到柜面上,等不忙的时候,再把这些书按照书柜区-排-号先排好,然后一次性归位

用方法1,管理员需要进出(IO)藏书区100次,不停的登高爬低完成图书归位操作,累死累活,效率很差。

用方法2,管理员只需要进出(IO)藏书区1次,对同一个位置的书,不管多少,都只要爬一次楼梯,大大减轻了管理员的工作量。

为什么对于非聚集索引(非唯一)的插入和更新有效?

还是用还书的例子来说,还一本书A到图书馆,管理员要判断一下这本书是不是唯一的,他在柜台上是看不到的,必须爬到指定位置去确认,这个过程其实已经产生了一次IO操作,相当于没有节省任何操作。

所以这个buffer只能处理非唯一的插入,不要求判断是否唯一。

三、自适应哈希索引(Adaptive Hash Index)

此索引非彼索引。Innodb存储引擎会监控对表上普通索引的查找,如果发现某索引被频繁访问,则该索引成为热数据,建立哈希索引可以带来速度的提升。看下图,AHI的位置在普通索引之前,查询时先查AHI,后查普通索引。

产生AHI的条件

通过主键查询,或者通过联合索引(a,b)查询,比如select * from t where a=xxx或select * from t where a=xxx and b=yyy;

以该模式查询至少100次。

页通过该模式访问至少N次,N=页中的记录数*1/16;

AHI只对等值查询有效,对范围查询无效。

另外,我们要知道,AHI是InnoDB控制的,因此我们不能对AHI进行干预。我们可以查看AHI的是否开启,默认是开启ON状态。

show variables like 'innodb_adaptive_hash_index'\G;

通过命令我们可以查到AHI的使用状况。

show engine innodb status\G; 

hash表示系统使用AHI查询的速度,non-hash是没有使用AHI的查询速度。如果读者想要看到数值,可以连续查询某数据100次以上,则可以看到hash的值。

select * from t where id=1;show engine innodb status\G;

select * from t where id>1;show engine innodb status\G;

Java_苏先生:专注于Java开发技术的研究与知识分享!
————END————

  • 点赞(感谢)
  • ...
  • 转发(感谢)
  • ...
  • 关注(感谢)
  • ...

你可能感兴趣的:(MySQL InnoDB存储引擎体系架构 —— 内存管理)