【InnoDB】缓冲池

 


如下的资料总结自:官方文档和《MySQL技术内幕-INNODB存储引擎》一书。前端

对INNODB存储引擎缓冲池的那一段描述来自博文:http://www.ywnds.com/?p=9886 说句实话这片博文写的很清楚,经过问答形式加紧逻辑性!node

这篇文字会详细的说明INNODB存储引擎的体系结构及特性。mysql

  • INNODB存储引擎的内存管理
  • Checkpoint技术
  • INNODB存储引擎的关键特性
    •   插入缓冲
    •       DOUBLEWRITE
    •       AHI(自适应哈希索引)
    •       异步IO
    •       刷新临近页
 

INNODB的体系结构

首先经过一张图来讲明INNODB存储引擎的体系结构。算法

INNODB存储引擎有多个内存块,这些内存块组成了一个大的内存池。(innodb_buffer_size)sql

INNODB的后台线程主要做用:刷新内存池中的数据,保证缓冲池中的内存缓存的是最近的数据。 其二:将已修改的数据文件刷新到磁盘文件,同时保证数据库发生异常的状况下INNODB能恢复到正常状态。数据库

在上一篇博文中说明了INNODB存储引擎后台线程的具体做用: CLICK HERE!缓存

上面的图片只是简单地说明了INNODB存储引擎的做用,下面是一张详细的图,说明了INNODB在具体是怎么样作这些事的!(图片来自官方网站的MySQL5.7的文档)服务器

下面咱们会详细的说明这张图的具体是怎么工做的!数据结构

 

缓冲池

INNODB存储引擎是基于磁盘存储的,并将其记录按照页的方式进行管理。在数据库系统中因为CPU速度与磁盘速度之间的鸿沟,基于磁盘的数据库系统一般使用缓冲技术来提升数据的总体性能。

缓冲池简单来讲就是一块内存区域.经过内存的速度来弥补磁盘速度较慢对数据库性能的影响。在数据库中进行读取页的操做,首先将从磁盘读到的页存放在缓冲池中(这个过程称做FIX),下一次读取相同的页时,首先判断该页是否是在缓冲池中,若在,称该页在缓冲池中被命中,直接读取该页。不然,读取磁盘上的页。

对于数据库中页的修改操做,首先修改在缓冲池中页,而后再以必定的频率刷新到磁盘,并非每次页发生改变就刷新回磁盘,而是经过一种叫作checkpoint的机制把页刷新会磁盘。

所以数据的操做都是对缓冲池进行的操做,而不是磁盘。

缓冲池的大小设置:

缓冲池配置能够经过INNODB_BUFFER_POOL_SZIE来设置,官方文档建议,缓冲池的大小最多应设置为物理内存的80%,正常使用能够设置为(50%~80%)之间。

缓冲池是一块内存,INNODB存储引擎是经过页的方式对这块内存进行管理的。缓冲池中存储的页有: 索引页,数据页,插入缓冲,自适应哈希索引(AHI),INNODB存储的锁信息,数据字典信息等。缓冲池中的索引页和数据页只是占据了缓冲池的很大一部分而已。如图(图片地址

 

从INNODB1.0.x开始,容许有多个缓冲池实例。每一个页根据哈希值平均分配到不一样的缓冲池实例中。这样作的好处是减小数据库内部的资源竞争,增长数据库并发处理能力。

innodb_buffer_pool_instances: 设置有多少个缓冲池。一般建议把缓冲池个数设置为CPU的个数。
在使用show engine innodb status\G的时候会以---BUFFER POOL 5的形式分别标识每个bp,全部的bp会均分INNODB_BUFFER_POOL_SZIE的大小。

缓冲池的管理

缓冲池的结构描述(或者组织形式,不太准确):

咱们已经知道这个Buffer Pool实际上是一片连续的内存空间,那如今就面临这个问题了:怎么将磁盘上的页缓存到内存中的Buffer Pool中呢?直接把须要缓存的页向Buffer Pool里一个一个往里怼么?不不不,为了更好的管理这些被缓存的页,InnoDB为每个缓存页都建立了一些所谓的控制信息,这些控制信息包括该页所属的表空间编号(space id)、页号(page number)、页在Buffer Pool中的地址,一些锁信息以及LSN信息(锁和LSN这里能够先忽略),固然还有一些别的控制信息。

每一个缓存页对应的控制信息占用的内存大小是相同的,咱们就把每一个页对应的控制信息占用的一块内存称为一个控制块吧,控制块和缓存页是一一对应的,它们都被存放到 Buffer Pool 中,其中控制块被存放到 Buffer Pool 的前边,缓存页被存放到 Buffer Pool 后边,因此整个Buffer Pool对应的内存空间看起来就是这样的:

控制块和缓存页之间的那个碎片是个什么呢?你想一想啊,每个控制块都对应一个缓存页,那在分配足够多的控制块和缓存页后,可能剩余的那点儿空间不够一对控制块和缓存页的大小,天然就用不到喽,这个用不到的那点儿内存空间就被称为碎片了。固然,若是你把Buffer Pool的大小设置的刚恰好的话,也可能不会产生碎片~

前面咱们知道了缓冲池的结构。接下来讲InnoDB存储引擎是怎么对缓冲池进行管理的?

当咱们最初启动MySQL服务器的时候,须要完成对Buffer Pool的初始化过程,就是分配Buffer Pool的内存空间,把它划分红若干对控制块和缓存页。可是此时并无真实的磁盘页被缓存到Buffer Pool中(由于尚未用到),以后随着程序的运行,会不断的有磁盘上的页被缓存到Buffer Pool中,那么问题来了,从磁盘上读取一个页到Buffer Pool中的时候该放到哪一个缓存页的位置呢?或者说怎么区分Buffer Pool中哪些缓存页是空闲的,哪些已经被使用了呢?咱们最好在某个地方记录一下哪些页是可用的,咱们能够把全部空闲的页包装成一个节点组成一个链表,这个链表也能够被称做Free链表(或者说空闲链表)。由于刚刚完成初始化的Buffer Pool中全部的缓存页都是空闲的,因此每个缓存页都会被加入到Free链表中,假设该Buffer Pool中可容纳的缓存页数量为n,那增长了Free链表的效果图就是这样的:

从图中能够看出,咱们为了管理好这个Free链表,特地为这个链表定义了一个控制信息,里边儿包含着链表的头节点地址,尾节点地址,以及当前链表中节点的数量等信息。咱们在每一个Free链表的节点中都记录了某个缓存页控制块的地址,而每一个缓存页控制块都记录着对应的缓存页地址,因此至关于每一个Free链表节点都对应一个空闲的缓存页。

有了这个Free链表事儿就好办了,每当须要从磁盘中加载一个页到Buffer Pool中时,就从Free链表中取一个空闲的缓存页,而且把该缓存页对应的控制块的信息填上,而后把该缓存页对应的Free链表节点从链表中移除,表示该缓存页已经被使用了,而且把改页写入LRU链表!

不要由于走的太远而忘记为何出发。
简单回顾一下,为何讲free list?是为了讲怎么管理buffer pool对吧。那free list就至关因而数据库服务刚刚启动没有数据页时,维护buffer pool的空闲缓存页的数据结构。

下面再来简单地回顾Buffer Pool的工做机制。Buffer Pool两个最主要的功能:一个是加速读,一个是加速写。加速读呢? 就是当须要访问一个数据页面的时候,若是这个页面已经在缓存池中,那么就再也不须要访问磁盘,直接从缓冲池中就能获取这个页面的内容。加速写呢?就是当须要修改一个页面的时候,先将这个页面在缓冲池中进行修改,记下相关的重作日志,这个页面的修改就算已经完成了。至于这个被修改的页面何时真正刷新到磁盘,这个是后台刷新线程来完成的。

在初始化的时候,bp中全部的页都是空闲页(也就是free list的页),须要读数据时,就会从free链表中申请页,由于物理内存不可能无限增大,可是数据库的数据倒是在不停增大的,因此free链表的页是会用完的,这时候应该怎么办?这时候咱们能够考虑把已经缓存的页从bp中删除一部分,那么究竟采用什么样的方式来删除,究竟该删除哪些已经缓存的页?

为了回答这个问题,咱们还须要回到咱们设立Buffer Pool的初衷,咱们就是想减小和磁盘的I/O交互,最好每次在访问某个页的时候它都已经被缓存到Buffer Pool中了。假设咱们一共访问了n次页,那么被访问的页已经在缓存中的次数除以n就是所谓的缓存命中率,咱们的指望就是让缓存命中率越高越好

怎么提升缓存命中率呢?InnoDB Buffer Pool采用经典的LRU算法来进行页面淘汰,以提升缓存命中率。当Buffer Pool中再也不有空闲的缓存页时,就须要淘汰掉部分最近不多使用的缓存页。不过,咱们怎么知道哪些缓存页最近频繁使用,哪些最近不多使用呢?呵呵,神奇的链表再一次派上了用场,咱们能够再建立一个链表,因为这个链表是为了按照最近最少使用的原则去淘汰缓存页的,因此这个链表能够被称为LRU链表(Least Recently Used)。当咱们须要访问某个页时,能够这样处理LRU链表

  • 若是该页不在Buffer Pool中,在把该页从磁盘加载到Buffer Pool中的缓存页时,就把该缓存页包装成节点塞到链表的头部。
  • 若是该页在Buffer Pool中,则直接把该页对应的LRU链表节点移动到链表的头部。

可是这样作会有一些性能上的问题,好比你的一次全表扫描或一次逻辑备份就把热数据给冲完了,就会致使致使缓冲池污染问题!Buffer Pool中的全部数据页都被换了一次血,其余查询语句在执行时又得执行一次从磁盘加载到Buffer Pool的操做,而这种全表扫描的语句执行的频率也不高,每次执行都要把Buffer Pool中的缓存页换一次血,这严重的影响到其余查询对 Buffer Pool 的使用,严重的下降了缓存命中率 !

因此InnoDB存储引擎对传统的LRU算法作了一些优化,在InnoDB中加入了midpoint。新读到的页,虽然是最新访问的页,但并非直接插入到LRU列表的首部,而是插入LRU列表的midpoint位置。这个算法称之为midpoint insertion stategy。默认配置插入到列表长度的5/8处。midpoint由参数innodb_old_blocks_pct控制。

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

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

【free 链表是空的,数据库刚初始化的时候产生的,当须要读取数据时,会从free list中申请一个页,把从放入磁盘读取的数据放入这个申请的页中,这个页的集合叫LRU链表】

上面说到了读数据,下面说明写数据:

前面咱们讲到页面更新是在缓存池中先进行的,那它就和磁盘上的页不一致了,这样的缓存页也被称为脏页(英文名:dirty page)。因此须要考虑这些被修改的页面何时刷新到磁盘?以什么样的顺序刷新到磁盘?固然,最简单的作法就是每发生一次修改就当即同步到磁盘上对应的页上,可是频繁的往磁盘中写数据会严重的影响程序的性能(毕竟磁盘慢的像乌龟同样)。因此每次修改缓存页后,咱们并不着急当即把修改同步到磁盘上,而是在将来的某个时间点进行同步,由后台刷新线程依次刷新到磁盘,实现修改落地到磁盘。

可是若是不当即同步到磁盘的话,那以后再同步的时候咱们怎么知道Buffer Pool中哪些页是脏页,哪些页历来没被修改过呢?总不能把全部的缓存页都同步到磁盘上吧,假如Buffer Pool被设置的很大,比方说300G,那一次性同步这么多数据岂不是要慢死!因此,咱们不得再也不建立一个存储脏页的链表,凡是在LRU链表中被修改过的页都须要加入这个链表中,由于这个链表中的页都是须要被刷新到磁盘上的,因此也叫FLUSH链表,有时候也会被简写为FLU链表。链表的构造和Free链表差很少,这就不赘述了。这里的脏页修改指的此页被加载进Buffer Pool后第一次被修改,只有第一次被修改时才须要加入FLUSH链表(代码中是根据Page头部的oldest_modification == 0来判断是不是第一次修改),若是这个页被再次修改就不会再放到FLUSH链表了,由于已经存在。须要注意的是,脏页数据实际还在LRU链表中,而FLUSH链表中的脏页记录只是经过指针指向LRU链表中的脏页。而且在FLUSH链表中的脏页是根据oldest_lsn(这个值表示这个页第一次被更改时的lsn号,对应值oldest_modification,每一个页头部记录)进行排序刷新到磁盘的,值越小表示要最早被刷新,避免数据不一致。

【理解脏页的概念?脏页是bp中被修改的页,脏页寄存在与lru链表中,也存在与flush链表中,flush链表中存在的是一个指向lru链表中具体数据的指针。所以只有lru链表中的页第一次别修改时,对应的指针才会存入到flush中,若之后再修改这个页,则是直接更新对应的数据。】

这三个重要列表(LRU list, free list,flush list)的关系能够用下图表示:

Free链表跟LRU链表的关系是相互流通的,页在这两个链表间来回置换。而FLUSH链表记录了脏页数据,也是经过指针指向了LRU链表,因此图中FLUSH链表被LRU链表包裹。

 

缓存中页的定位:

咱们前边说过,当咱们须要访问某个页中的数据时,就会把该页加载到Buffer Pool中,若是该页已经在Buffer Pool中的话直接使用就能够了。那么问题也就来了,咱们怎么知道该页在不在Buffer Pool中呢?难不成须要依次遍历Buffer Pool中各个缓存页么?一个Buffer Pool中的缓存页这么多都遍历完岂不是要累死?

再回头想一想,咱们实际上是根据表空间号 + 页号来定位一个页的,也就至关于表空间号 + 页号是一个key,缓存页就是对应的value,怎么经过一个key来快速找着一个value呢?那确定是哈希表了。

因此咱们能够用表空间号 + 页号做为key,缓存页做为value建立一个哈希表,在须要访问某个页的数据时,先从哈希表中根据表空间号 + 页号看看有没有对应的缓存页,若是有,直接使用该缓存页就好,若是没有,那就从Free链表中选一个空闲的缓存页,而后把磁盘中对应的页加载到该缓存页的位置。

 

上面基本说明了bp是怎么工做的,接下来咱们看一个实例的bp信息。

mysql> show engine innodb ststus;
....
BUFFER POOL AND MEMORY ---------------------- Total large memory allocated 5502402560 #总的内存是多少,字节为单位 Dictionary memory allocated 991733 #为数据字典分配的总内存 Buffer pool size 327680 #总的bp有多少个页,每一个页默认大小为16K(innodb_page_size的数值) Free buffers 8192 #当数据库刚启动时,bp中没有数据,会含有许多16KB的块,这些块就是free buffer。当读取数据时,就从free list中申请一个块,而后把这个块放入lru列表中.Free buffers表示当前free列表页中的数量。 Database pages 490679 #表示的就是lru列表中的页,也就是数据页。(可能状况是free buffer+database pages的数量之和等于bp,由于缓冲池中还可能会被分配自适应哈希索引,lock信息,insert buffer等页,这部分页不须要lru算法维护,所以不存在lru列表中) Old database pages 180966 # lru列表中old部分的页数量 Modified db pages 0 # 脏页的数量。flush列表 Percent of dirty pages(LRU & free pages): 0.000 # Max dirty pages percent: 75.000 # Pending reads 0 # 等待读入缓冲池的缓冲池页数。 Pending writes: LRU 0, flush list 0, single page 0 # Pages made young 452994, not young 1694417 0.00 youngs/s, 0.00 non-youngs/s (将页从lru列表的old部分加入到new部分时,称此时的操做为page made young.而由于innodb_old_blocks_time的设置致使页没有从old部分移动到new部分的操做称为page not made young.--pages made young:显示了lru列表中页移动到前端的次数。young/s, non-young/s表示每秒这两类操做的次数。) Pages read 1436912, created 4603153, written 3896513 0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s LRU len: 490679, unzip_LRU len: 49074 I/O sum[0]:cur[0], unzip sum[0]:cur[0]
....
#这个页数据的总体介绍能够查看官方文档: https://dev.mysql.com/doc/refman/5.7/en/innodb-buffer-pool.html

咱们还可使用统计表查看以下:
mysql> use information_schema;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> select * from INNODB_BUFFER_POOL_STATS \G
*************************** 1. row ***************************
                         POOL_ID: 0
                       POOL_SIZE: 8191
                    FREE_BUFFERS: 7005
                  DATABASE_PAGES: 1186
              OLD_DATABASE_PAGES: 448
         MODIFIED_DATABASE_PAGES: 0
              PENDING_DECOMPRESS: 0
                   PENDING_READS: 0
               PENDING_FLUSH_LRU: 0
              PENDING_FLUSH_LIST: 0
                PAGES_MADE_YOUNG: 0
            PAGES_NOT_MADE_YOUNG: 0
           PAGES_MADE_YOUNG_RATE: 0
       PAGES_MADE_NOT_YOUNG_RATE: 0
               NUMBER_PAGES_READ: 1126
            NUMBER_PAGES_CREATED: 60
            NUMBER_PAGES_WRITTEN: 70
                 PAGES_READ_RATE: 0
               PAGES_CREATE_RATE: 0
              PAGES_WRITTEN_RATE: 0
                NUMBER_PAGES_GET: 22583
                        HIT_RATE: 0
    YOUNG_MAKE_PER_THOUSAND_GETS: 0
NOT_YOUNG_MAKE_PER_THOUSAND_GETS: 0
         NUMBER_PAGES_READ_AHEAD: 384
       NUMBER_READ_AHEAD_EVICTED: 0
                 READ_AHEAD_RATE: 0
         READ_AHEAD_EVICTED_RATE: 0
                    LRU_IO_TOTAL: 0
                  LRU_IO_CURRENT: 0
                UNCOMPRESS_TOTAL: 0
              UNCOMPRESS_CURRENT: 0
1 row in set (0.00 sec)

#这个表的字段信息和上面命令输出的信息均可以用来查看当前bp的统计信息InnoDB
 

 

 数据访问机制

1. 当访问的页面在缓存池中命中,则直接从缓冲池中访问该页面。另外为了不查询数据页时扫描LRU,还为每一个buffer pool instance维护了一个page hash,经过space id和page id能够直接找到对应的page。通常状况下,当咱们须要读入一个Page时,首先根据space id(space id对应的是表)和page id找到对应的buffer pool instance。而后查询page hash,若是page hash中没有,则表示须要从磁盘读取。

2. 若是没有命中,则须要将这个页面从磁盘上加载到缓存池中,所以须要在缓存池中的空闲列表中找一个空闲的内存块来缓存这个从磁盘读入的页面。

3. 但存在空闲内存块被使用完的状况,不保证必定有空闲的内存块。假如空闲列表为空,没有空闲的内存块,则须要想办法去产生空闲的内存块。

4. 首先去LRU列表中找能够替换的内存页面,查找方向是从列表的尾部开始找,若是找到能够替换的页面,将其从LRU列表中摘除,加入空闲列表,而后再去空闲列表中找空闲的内存块。第一次查找最多只扫描100个页面,循环进行到第二次时,会查找深度就是整个LRU列表。这就是LRU列表中的页面淘汰机制。

5. 若是在LRU列表中没有找到能够替换的页,则进行单页刷新,将脏页刷新到磁盘以后,而后将释放的内存块加入到空闲列表。而后再去空闲列表中取。为何只作单页刷新呢?由于这个函数的目的是获取空闲内存页,进行脏页刷新是不得已而为之,因此只会进行一个页面的刷新,目的是为了尽快的获取空闲内存块。

由于空闲列表是一个公共的列表,全部的用户线程均可以使用,存在争用的状况。所以,本身产生的空闲内存块有可能会恰好被其余线程所使用,因此用户线程可能会重复执行上面的查找流程,直到找到空闲的内存块为止。

经过数据页访问机制,能够知道其中当无空闲页时产生空闲页就成为一个必需要作的事情了。若是须要刷新脏页来产生空闲页面或者须要扫描整个LRU列表来产生空闲页面的时候,查找空闲内存块的时间就会延长,这个是一个bad case,是咱们但愿尽可能避免的。所以,innodb buffer pool中存在大量能够替换的页面,或者free列表中一直存在着空闲内存块,对快速获取到空闲内存块起决定性的做用。在innodb buffer pool的机制中,是采用何种方式来产生的空闲内存块,以及能够替换的内存页的呢?这就是咱们下面要讲的内容——经过后台刷新机制来产生空闲的内存块以及能够替换的页面。

 

 checkpoint技术

 当前事务数据库系统广泛采用了write ahead log策略,即当事务提交时,先写重作日志,再修改页。当因为数据库宕机而致使数据丢失时,经过重作日志来完成数据的恢复。

想一种状况,当重作日志变得很大时,数据库的恢复时间就会变得很长,恢复代价变得很大?

checkpoint技术主要目的是:

  • 缩短数据库恢复时间
  • 缓冲池不够用时,将脏页刷新到磁盘。
  • 重作日志不可用时,刷新脏页。

 当数据库发生宕机时,数据库不须要全部的重作日志,由于checkpoint以前的页都已经刷新回磁盘,故数据库只须要对checkpoint以后的重作日志进行恢复便可。

此外当缓冲池不够用时,根据lru算法会释放最近最少使用的页,若此页为脏页,那么就须要强制执行checkpoint,将脏页刷新回磁盘。

 由于当前事务数据库系统对重作日志的设计都是循环使用的,在写入重作日志时,若这部分重作日志不可用【是由于数据库在宕机恢复时若须要这使用这部分日志,若此时想要使用这部分重作日志(前面的不可用状态的重作日志)】则必须强制checkpoint,将缓冲池中的页刷新到对应当前重作日志的位置。

若重作日志能够被重用的部分是指这些重作日志已经再也不须要(缓冲池中的页和重作日志的位置吻合),那就能够直接覆盖。

 在这里刷新缓冲池中页的时候,咱们提到过,要把页刷新道道重作日志的位置,那么INNODB是怎么肯定这些位置的?

INNODB使用LSN(log sequenct number)来标识页刷新的位置。【在前面的字节数上加上写入的字节数】

 每一个页都有对应LSN数值,重作日志有LSN,checkpoint也有LSN。

mysql> show engine innodb status\G
......
LOG
---
Log sequence number 293633237 #当前缓冲池中的lsn的值,也就是redo log的lsn
Log flushed up to   293633237 #当前磁盘中的lsn的值,是刷redo log file flush to disk中的lsn;
Pages flushed up to 293633237 #是已经刷到磁盘数据页上的LSN;
Last checkpoint at  293633228 #上一次刷新的LSN
.....
 

在INNODB存储引擎中,checkpint发生的时间,条件及脏页选择都很复杂,而checkpoint所作的事情就是把缓冲池中的脏页刷新到磁盘。不一样之处在于每次刷新多少脏页以及何时出发checkpoint?【这是只是简单说明下】

在INNODB存储引擎内部有两种checkpoint方式:

  • Sharp Checkpoint
  • Fuzzy Checkpoint

Sharp Checkpoint发生在将数据库关机时将全部的脏页刷新回磁盘,这是默认的工做方式,即参数innodb_fast_shutdown=1.

可是若在数据库运行时也使用Sharp Checkpoint,那么数据库的性能就会受到影响。故在INNODB内部使用Fuzzy Checkpoint的刷新方式,即每次只刷新一部分脏页,而不是刷新全部的脏页。

在INNODB内部在发生以下状况时,会进行fuzzy checkpoint刷新。

Master Thread Checkpoint: 【异步刷新,每秒或每10秒从缓冲池脏页列表刷新必定比例的页回磁盘。异步刷新,即此时InnoDB存储引擎能够进行其余操做,用户查询线程不会受阻】
FLUSH_LRU_LIST Checkpoint:InnoDB存储引擎须要保证LRU列表中差很少有100个空闲页可供使用。在InnoDB 1.1.x版本以前,用户查询线程会检查LRU列表是否有足够的空间操做。若是没有,根据LRU算法,溢出LRU列表尾端的页,若是这些页有脏页,须要进行checkpoint。所以叫:flush_lru_list checkpoint。
InnoDB 1.2.x开始,这个检查放在了单独的进程(Page Cleaner)中进行,而且可使用innodb_lru_scan_depth 参数控制LRU列表中可用页的数量,默认是1024!好处:1.减小master Thread的压力 2.减轻用户线程阻塞。 异步/同步 Checkpoint:重作日志不可用时,须要强制将一些页刷新回磁盘,而此时脏页是从脏页列表中选择的。 脏页太多时强制checkpoint:脏页数量太多时,强制进行checkpoint,当缓冲池中脏页的数量占据超过innodb_max_dirty_pages_pct设定的值时,就进行强制刷新。默认数值是75%。
 
 

INNODB的关键特性

插入缓冲

INNODB在插入非汇集的非惟一性索引时,会随机插入的数据,这就会致使性能降低。所以INNODB采用了insert buffer来完成非汇集非惟一性索引的插入。当插入这些索引时,不是每一次直接插入到索引页中,而是先判断插入的非汇集索引是否在缓冲池中,若在,则直接插入;若不在,则先放入到一个insert buffer对象中,好似欺骗。而后再以必定的频率进行insert buffer和辅助所引页子节点的合并操做,这时一般能将多个插入合并到一个操做中,这就大大提升了对于非汇集索引的插入性能。

insert buffer须要同时知足如下两个条件:

  • 索引时辅助索引
  • 索引不是惟一索引

辅助索引不能是惟一的,由于在插入缓冲时,数据库并不去查找所引页来判断记录的惟一性。若是去查找确定致使又忽悠离散型读状况发生,从而致使insert buffer失去了意义。

INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 0, seg size 2, 0 merges
#size:表示已经合并记录页的数量,free list:表示空闲列表的长度,seg size显示当前insert buffer的大小, mergers:表示合并页的数量 merged operations: insert 0, delete mark 0, delete 0 discarded operations: #表示change buffer发生merge,表已经被删除,此时就无需再将记录合并到辅助索引中。 insert 0, delete mark 0, delete 0 Hash table size 34673, node heap has 0 buffer(s) Hash table size 34673, node heap has 0 buffer(s) Hash table size 34673, node heap has 0 buffer(s) Hash table size 34673, node heap has 0 buffer(s) Hash table size 34673, node heap has 0 buffer(s) Hash table size 34673, node heap has 0 buffer(s) Hash table size 34673, node heap has 0 buffer(s) Hash table size 34673, node heap has 0 buffer(s) 0.00 hash searches/s, 0.00 non-hash searches/s

 

change buffer

INNODB从1.0.x开始引入了Change Buffer,从这个版本开始INNODB存储引擎能够对DNL操做---insert(insert buffer), delete(dlete buffer),update(purge buffer)都进行缓冲。

和以前同样change buffer的对象依然是辅助的非惟一索引。

对一条记录进行update操做包含两个过程:1将记录标记为删除,2:将记录删除。delete buffer对应update的第一个阶段,purge buffer对应update的第二个阶段。

同时INNODB存储引擎还提供了参数innodb_change_buffering ,用来开启各类buffer的选项。可选择的值以下:

inserts, deletes, purges, changes, all, none.
#changes:表示inserts和deletes。
#all:表示启用所用
#none:表示所有关闭。默认是all

在写密集的状况下,change buffer会占用过多的缓冲池资源,在INNODB1.2版本中可使用innodb_change_buffer_max_size参数进行控制,默认数值是25,表示1/4.

inert buffer的内部实现:

【站位】

两次写

在数据库发生宕机时,可能INNODB存储引擎正在写入某个页到表中,而这个页只写了一部分,好比16KB的页,只写了前4KB,以后就发生了宕机,这种状况被称为部分写失效。

doublewrite由两部分组成,一部分是内存中的double buffer,大小为2M,另外一部分是物理磁盘上共享表空间的连续的128个页,即2个区,大小为2M。在对缓冲池进行脏页刷新时,并不直接写磁盘,而是会经过memcpy函数将脏页首先复制到内存中的doublewrite buffer。以后经过doublewrite buffer再分两次,每次1M顺利地写入共享表空间的物理磁盘上,而后立刻调用fsync函数,同步磁盘,避免写缓冲带来的问题。在这个过程当中doublewriter是连续的,所以开销不大。

 

转自:http://www.javashuo.com/article/p-gkcxducm-k.html

相关文章
相关标签/搜索