MySQL 学习总结 之 缓冲池(Buffer Pool)

目录

1 缓冲池(Buffer Pool)的地位

2 缓冲池 的内容结构

3 缓存过程

3.1 Buffer Pool 的初始化

3.2 Free 链表记录空闲缓存页

3.3 Flush 链表记录脏缓存页

3.4 LRU 链表记录缓存页的命中率

4 LRU原理

5 总结

6 常见问题


1 缓冲池(Buffer Pool)的地位

应用系统分层架构,为了加速数据访问,会把最常访问的数据,放在缓存(cache)里,避免每次都去访问数据库。

操作系统,会有缓冲池(buffer pool)机制,避免每次访问磁盘,以加速数据的访问。

MySQL作为一个存储系统,同样具有缓冲池(buffer pool)机制,以避免每次查询数据都进行磁盘IO。

因为为了提高 MySQL 的并发性能,使用到的数据都会缓存在缓冲池中,然后所有的增删改查操作都将在缓冲池中执行。通过这种方式,保证每个更新请求,尽量就是只更新内存,然后往磁盘顺序写日志文件。更新内存的性能是极高的,然后顺序写磁盘上的日志文件的性能也是比较高的,因为顺序写磁盘文件,他的性能要远高于随机读写磁盘文件。

2 缓冲池 的内容结构

InnoDB的缓冲池缓存什么?有什么用?

 缓存表数据与索引数据,把磁盘上的数据加载到缓冲池,避免每次访问都进行磁盘IO,起到加速访问的作用。

速度快,那为啥不把所有数据都放到缓冲池里

 凡事都具备两面性,抛开数据易失性不说,访问快速的反面是存储容量小:

  (1)缓存访问快,但容量小,数据库存储了200G数据,缓存容量可能只有64G;

  (2)内存访问快,但容量小,买一台笔记本磁盘有2T,内存可能只有16G;

 因此,只能把“最热”的数据放到“最近”的地方,以“最大限度”的降低磁盘访问。

缓冲池(Buffer Pool)的默认大小为 128M,可通过 innodb_buffer_pool_size 参数来配置

当 SQL 执行时,用到的相关表的数据行,会将这些数据行都缓存到 Buffer Pool 中。

但是我们可以想象一下,如果像上面的机制那么简单,那么如果是分页的话,不断地查询就要不断地将磁盘文件中数据页的数据缓存到 Buffer Pool 中了,那么这时候缓存池这个机制就显得没什么用了,每次查询还是会有一次或者多次的磁盘IO。

但是怎么缓存呢?

1、数据页概念

我们先了解一下数据页这个概念。它是 MySQL 抽象出来的数据单位,磁盘文件中就是存放了很多数据页,每个数据页里存放了很多行数据。

默认情况下,数据页的大小是 16kb。

所以对应的,在 Buffer Pool 中,也是以数据页为数据单位,存放着很多数据。但是我们通常叫做缓存页,因为 Buffer Pool 毕竟是一个缓冲池,并且里面的数据都是从磁盘文件中缓存到内存中。

所以,默认情况下缓存页的大小也是 16kb,因为它和磁盘文件中数据页是一一对应的。

所以,缓冲池和磁盘之间的数据交换的单位是数据页,包括从磁盘中读取数据到缓冲池和缓冲池中数据刷回磁盘中,如图所示:
在这里插入图片描述

2、怎么识别数据在哪个缓存页中?

到此,我们都知道 Buffer Pool 中是用缓存页来缓存数据的,但是我们怎么知道缓存页对应着哪个表,对应着哪个数据页呢?

所以每个缓存页都会对应着一个描述数据块,里面包含数据页所属的表空间、数据页的编号,缓存页在 Buffer Pool 中的地址等等。

描述数据块本身也是一块数据,它的大小大概是缓存页大小的5%左右,大概800个字节左右的大小。

描述如图所示:
在这里插入图片描述

如何管理与淘汰缓冲池,使得性能最大化呢?

在介绍具体细节之前,先介绍下“预读”的概念。

什么是预读?

  磁盘读写,并不是按需读取,而是按页读取,一次至少读一页数据(一般是4K),如果未来要读取的数据就在页中,就能够省去后续的磁盘IO,提高效率。

预读为什么有效?

  数据访问,通常都遵循“集中读写”的原则,使用一些数据,大概率会使用附近的数据,这就是所谓的“局部性原理”,它表明提前加载是有效的,确实能够减少磁盘IO。

按页(4K)读取,和InnoDB的缓冲池设计有啥关系?

 (1)磁盘访问按页读取能够提高性能,所以缓冲池一般也是按页缓存数据;

 (2)预读机制启示了我们,能把一些“可能要访问”的页提前加入缓冲池,避免未来的磁盘IO操作;

InnoDB是以什么算法,来管理这些缓冲页呢?

  最容易想到的,就是LRU(Least recently used)

画外音:memcache,OS都会用LRU来进行页置换管理,但MySQL的玩法并不一样。

3 缓存过程

3.1 Buffer Pool 的初始化

到此,我们都知道了,Buffer Pool 是缓存数据的数据单位为缓存页,利用描述数据块来标识缓存页。

那么,MySQL 启动时,是如何初始化 Buffer Pool 的呢?

1、MySQL 启动时,会根据参数 innodb_buffer_pool_size 的值来为 Buffer Pool 分配内存区域。

2、然后会按照缓存页的默认大小 16k 以及对应的描述数据块的 800个字节 左右大小,在 Buffer Pool 中划分中一个个的缓存页和一个个的描述数据库块。

3、注意,此时的缓存页和描述数据块都是空的,毕竟才刚启动 MySQL 呢。

3.2 Free 链表记录空闲缓存页

上面我们了解了 Buffer Pool 在 MySQL 启动时是如何初始化的。当 MySQL 启动后,会不断地有 SQL 请求进来,此时空先的缓存页就会不断地被使用。

那么, Buffer Pool 怎么知道哪些缓存页是空闲的呢?

1、Free 链表的使用原理

free 链表,它是一个双向链表,链表的每个节点就是一个个空闲的缓存页对应的描述数据块。

他本身其实就是由 Buffer Pool 里的描述数据块组成的,你可以认为是每个描述数据块里都有两个指针,一个是 free_pre 指针,一个是 free_next 指针,分别指向自己的上一个 free 链表的节点,以及下一个 free 链表的节点。

通过 Buffer Pool 中的描述数据块的 free_pre 和 free_next 两个指针,就可以把所有的描述数据块串成一个 free 链表。

下面我们可以用伪代码来描述一下 free 链表中描述数据块节点的数据结构:

DescriptionDataBlock{
    block_id = block1;
    free_pre = null;
    free_next = block2;
}

free 链表有一个基础节点,他会引用链表的头节点和尾节点,里面还存储了链表中有多少个描述数据块的节点,也就是有多少个空闲的缓存页。

下面我们也用伪代码来描述一下基础节点的数据结构:

FreeListBaseNode{
    start = block01;
    end = block03;   
    count = 2;
}

到此,free 链表就介绍完了。上面我们也介绍了 MySQL 启动时 Buffer Pool 的初始流程,接下来,我会将结合刚介绍完的 free 链表,讲解一下 SQL 进来时,磁盘数据页读取到 Buffer Pool 的缓存页的过程。但是,我们先要了解一下一个新概念:数据页缓存哈希表,它的 key 是表空间+数据页号,而 value 是对应缓存页的地址。

描述如图所示:
在这里插入图片描述

2、磁盘数据页读取到 Buffer Pool 的缓存页的过程

1、首先,SQL 进来时,判断数据对应的数据页能否在 数据页缓存哈希表里 找到对应的缓存页。

2、如果找到,将直接在 Buffer Pool 中进行增删改查。

3、如果找不到,则从 free 链表中找到一个空闲的缓存页,然后从磁盘文件中读取对应的数据页的数据到缓存页中,并且将数据页的信息和缓存页的地址写入到对应的描述数据块中,然后修改相关的描述数据块的 free_pre 指针和 free_next 指针,将使用了的描述数据块从 free 链表中移除。记得,还要在数据页缓存哈希表中写入对应的 key-value 对。最后也是在 Buffer Pool 中进行增删改查。

3.3 Flush 链表记录脏缓存页

1、脏页和脏数据

我们都知道 SQL 的增删改查都在 Buffer Pool 中执行,慢慢地,Buffer Pool 中的缓存页因为不断被修改而导致和磁盘文件中的数据不一致了,也就是 Buffer Pool 中会有很多个脏页,脏页里面很多脏数据。

所以,MySQL 会有一条后台线程,定时地将 Buffer Pool 中的脏页刷回到磁盘文件中。

但是,后台线程怎么知道哪些缓存页是脏页呢,不可能将全部的缓存页都往磁盘中刷吧,这会导致 MySQL 暂停一段时间。

2、MySQL 是怎么判断脏页的

我们引入一个和 free 链表类似的 flush 链表。他的本质也是通过缓存页的描述数据块中的两个指针,让修改过的缓存页的描述数据块能串成一个双向链表,这两指针大家可以认为是 flush_pre 指针和 flush_next 指针。

下面我用伪代码来描述一下:

DescriptionDataBlock{
    block_id = block1;
    // free 链表的
    free_pre = null;
    free_next = null;

    // flush 链表的
    flush_pre = null;
    flush_next = block2;
}

flush 链表也有对应的基础节点,也是包含链表的头节点和尾节点,还有就是修改过的缓存页的数量。

FlushListBaseNode{
    start = block1;
    end = block2;
    count = 2;
}

到这里,我们都知道,SQL 的增删改都会使得缓存页变为脏页,此时会修改脏页对应的描述数据块的 flush_pre 指针和 flush_next 指针,使得描述数据块加入到 flush 链表中,之后 MySQL 的后台线程就可以将这个脏页刷回到磁盘中。

描述如图所示:
在这里插入图片描述

3.4 LRU 链表记录缓存页的命中率

1、缓存命中率

我们都知道,当加载磁盘中的数据页到缓存中时,会从 free 链表找到空闲的缓存页,然后将数据加载到缓存页里。

但是缓存页总会有用完的时候,此时需要淘汰一下缓存页,将它刷入磁盘中,然后清空。

那么会选择谁淘汰呢?

那么必定会淘汰缓存命中率低的缓存页。

什么叫缓存命中率低:假如你有100次请求,有30次请求都是查询和修改缓存页一,直接操作缓存而不需要从磁盘加载,这就是缓存命中率高。而缓存页二自加载到 Buffer Pool 后,只被查询和修改过一次,之后的100次请求中甚至没有一次是查询和修改它的,这就是缓存命中率低了,因为大部分请求都是操作其他缓存页,甚至要从磁盘中加载。

2、lru 链表的使用原理

InnoDB 存储引擎是利用 lru 链表完成上面的缓存命中率的。lru 就是 Least Recently Used,最近最少使用的意思。

ps:lru 链表也是类似于 free 链表和 flush 链表的数据结构。

当有磁盘数据页加载数据到缓存页时,会将缓存页对应的描述数据块放入 lru 链表的头部;后续只要查询或者修改了缓存页的数据,也会将对应描述数据块移到 lru 链表的头部去。

此时,lru 链表尾部的描述数据块对应的缓存页,必定是命中率最低的,也就是使用最少的缓存页,所以优先被淘汰的肯定是它。

3、lru 链表存在的问题

lru 链表的使用当然不会像上面的那么简单。

因为 MySQL 为了提高性能,提供了一个机制:预读机制

当你从磁盘上加载一个数据页的时候,他可能会连带着把这个数据页相邻的其他数据页,也加载到缓存里去。这个机制会带来这么一个问题:连带的数据页可能在后面的查询或者修改中,并不会用到,但是它们却在 lru 链表的头部。

什么意思?

那就是,本来经常被用到的缓存页被压到 lru 链表的尾部去了,如果此时需要淘汰缓存页,命中率高的缓存页反而被淘汰掉了!

当然了,全表扫描也会带来同样的问题。

全表扫描会将表里所有的数据一次性加载到 Buffer Pool 来,但是却有很多数据在之后都不会用到。

4、冷热数据分离

什么是冷热分离?

简单点,就是将命中率高的数据和命中率低的数据分开,分成两块区域。

InnoDB 存储引擎就是利用冷热数据分离方案来解决上面的问题:将 lru 链表分为两部分,一部分是热数据区域链表,一部分是冷数据区域链表。

lru 链表的头节点指向热数据区域的链表头节点,lru 链表的尾节点指向冷数据区域的链表尾节点。

描述如图所示:
在这里插入图片描述

冷热分离的比例

由参数 innodb_old_blocks 控制,默认值为37,表示冷数据占所有数据的37%。

冷热分离的原理

磁盘中的数据页第一次加载到缓存页时,对应的描述数据块放到冷数据区域的链表头部,然后在 1s 后,如果再次访问这个缓存页,才会将缓存页对应的描述数据块移动到热数据区域的链表头部去。

这个 1s 由参数 innodb_old_blocks_time 指定,默认值是 1000 毫秒。

热数据区的优化

冷数据区的缓存页是在 1s 后再被访问到就移动到热数据区的链表头部。那么热数据区域的规则呢,是不是只要被访问就会移动到热数据区域的链表头部。

当然不是了。大家可以想一下,能留在热数据区域的缓存页,证明都是缓存命中率比较高的,会经常被访问到。如果每个缓存页被访问都移动到链表头部,那这个操作将会非常的频繁。

所以 InnoDB 存储引擎做了一个优化,只有在热数据区域的后 3/4 的缓存页被访问了,才会移动到链表头部;如果是热数据区域的前 1/4 的缓存页被访问到,它是不会被移动到链表头部去的。

lru 链表尾部的缓存页何时刷入磁盘

当 free 链表为空了,此时需要将数据页加载到缓冲池里,就会 lru 链表的冷数据区域尾部的缓存页刷入磁盘,然后清空,再加载数据页的数据。

一条后台线程,运行一个定时任务,定时将 lru 链表的冷数据区域的尾部的一些缓存页刷入磁盘,然后清空,最后把他们对应的描述数据块加入到 free 链表中去。

当然了,除了 lru 链表尾部的缓存页会被刷入磁盘,还有的就是 flush 链表的缓存页。

后台线程同时也会在 MySQL 不繁忙的时候,将 flush 链表中的缓存页刷入磁盘中,这些缓存页的描述数据块会从 lru 链表和 flush 链表中移除,并加入到 free 链表中。

4 LRU原理

传统的LRU是如何进行缓冲页管理?

最常见的玩法是,把入缓冲池的页放到LRU的头部,作为最近访问的元素,从而最晚被淘汰。这里又分两种情况:

 (1)页已经在缓冲池里,那就只做“移至”LRU头部的动作,而没有页被淘汰;

 (2)页不在缓冲池里,除了做“放入”LRU头部的动作,还要做“淘汰”LRU尾部页的动作;

 如上图,假如管理缓冲池的LRU长度为10,缓冲了页号为1,3,5…,40,7的页。

 假如,接下来要访问的数据在页号为4的页中:

 

 (1)页号为4的页,本来就在缓冲池里;

 (2)把页号为4的页,放到LRU的头部即可,没有页被淘汰;

画外音:为了减少数据移动,LRU一般用链表实现。

假如,再接下来要访问的数据在页号为50的页中:

 (1)页号为50的页,原来不在缓冲池里;

 (2)把页号为50的页,放到LRU头部,同时淘汰尾部页号为7的页;

传统的LRU缓冲池算法十分直观,OS,memcache等很多软件都在用,MySQL为啥这么矫情,不能直接用呢?

 这里有两个问题:

  (1)预读失效

  (2)缓冲池污染

什么是预读失效?

  由于预读(Read-Ahead),提前把页放入了缓冲池,但最终MySQL并没有从页中读取数据,称为预读失效。

如何对预读失效进行优化?

 要优化预读失效,思路是:

  (1)让预读失败的页,停留在缓冲池LRU里的时间尽可能短;

  (2)让真正被读取的页,才挪到缓冲池LRU的头部;

 以保证,真正被读取的热数据留在缓冲池里的时间尽可能长。

具体方法是:

(1)将LRU分为两个部分:

  • 新生代(new sublist)

  • 老生代(old sublist)

(2)新老生代收尾相连,即:新生代的尾(tail)连接着老生代的头(head);

(3)新页(例如被预读的页)加入缓冲池时,只加入到老生代头部:

  • 如果数据真正被读取(预读成功),才会加入到新生代的头部

  • 如果数据没有被读取,则会比新生代里的“热数据页”更早被淘汰出缓冲池

 

举个例子,整个缓冲池LRU如上图:

 (1)整个LRU长度是10;

 (2)前70%是新生代;

 (3)后30%是老生代;

 (4)新老生代首尾相连;

 

假如有一个页号为50的新页被预读加入缓冲池:

 (1)50只会从老生代头部插入,老生代尾部(也是整体尾部)的页会被淘汰掉;

 (2)假设50这一页不会被真正读取,即预读失败,它将比新生代的数据更早淘汰出缓冲池;

 

假如50这一页立刻被读取到,例如SQL访问了页内的行row数据:

 (1)它会被立刻加入到新生代的头部;

 (2)新生代的页会被挤到老生代,此时并不会有页面被真正淘汰;

改进版缓冲池LRU能够很好的解决“预读失败”的问题

画外音:但也不要因噎废食,因为害怕预读失败而取消预读策略,大部分情况下,局部性原理是成立的,预读是有效的。

新老生代改进版LRU仍然解决不了缓冲池污染的问题。

什么是MySQL缓冲池污染?

  当某一个SQL语句,要批量扫描大量数据时,可能导致把缓冲池的所有页都替换出去,导致大量热数据被换出,MySQL性能急剧下降,这种情况叫缓冲池污染。

 

 例如,有一个数据量较大的用户表,当执行:

  select * from user where name like "%shenjian%";

 虽然结果集可能只有少量数据,但这类like不能命中索引,必须全表扫描,就需要访问大量的页:

  (1)把页加到缓冲池(插入老生代头部);

  (2)从页里读出相关的row(插入新生代头部);

  (3)row里的name字段和字符串shenjian进行比较,如果符合条件,加入到结果集中;

  (4)…直到扫描完所有页中的所有row…

 

 如此一来,所有的数据页都会被加载到新生代的头部,但只会访问一次,真正的热数据被大量换出。

 

怎么这类扫码大量数据导致的缓冲池污染问题呢?

 MySQL缓冲池加入了一个“老生代停留时间窗口”的机制:

  (1)假设T=老生代停留时间窗口;

  (2)插入老生代头部的页,即使立刻被访问,并不会立刻放入新生代头部;

  (3)只有满足被访问”并且“在老生代停留时间”大于T,才会被放入新生代头部;

 

  继续举例,假如批量数据扫描,有51,52,53,54,55等五个页面将要依次被访问。

 

  如果没有“老生代停留时间窗口”的策略,这些批量被访问的页面,会换出大量热数据。

  加入“老生代停留时间窗口”策略后,短时间内被大量加载的页,并不会立刻插入新生代头部,而是优先淘汰那些,短期内仅仅访问了一次的页。

 

  而只有在老生代呆的时间足够久,停留时间大于T,才会被插入新生代头部。

 

5 总结

(1)缓冲池(buffer pool)是一种常见的降低磁盘访问的机制;

(2)缓冲池通常以页(page)为单位缓存数据;

(3)缓冲池的常见管理算法是LRU,memcache,OS,InnoDB都使用了这种算法;

(4)InnoDB对普通LRU进行了优化:

  • 将缓冲池分为老生代和新生代,入缓冲池的页,优先进入老生代,页被访问,才进入新生代,以解决预读失效的问题

  • 页被访问,且在老生代停留时间超过配置阈值的,才进入新生代,以解决批量数据访问,大量热数据淘汰的问题

下面简单总结一下 Buffer Pool 从初始化到使用的整个流程。

1、MySQL 启动时会根据分配指定大小内存给 Buffer Pool,并且会创建一个个描述数据块和缓存页。

2、SQL 进来时,首先会根据数据的表空间和数据页编号查询 数据页缓存哈希表 中是否有对应的缓存页。

3、如果有对应的缓存页,则直接在 Buffer Pool中执行。

4、如果没有,则检查 free 链表看看有没有空闲的缓存页。

5、如果有空闲的缓存页,则从磁盘中加载对应的数据页,然后将描述数据块从 free 链表中移除,并且加入到 lru 链表的冷数据区域的链表头部。后面如果被修改了,还需要加入到 flush 链表中。

6、如果没有空闲的缓存页,则将 lru 链表的冷数据区域的链表尾部的缓存页刷回磁盘,然后清空,接着将数据页的数据加载到缓存页中,并且描述数据块会加入到 lru 链表的冷数据区域的链表头部。后面如果被修改了,还需要加入到 flush 链表中。

7、5或者6后,就接着在 Buffer Pool 中执行增删改查。

注意:5和6中,缓存页加入到冷数据区域的链表头部后,如果在 1s 后被访问,则将入到热数据区域的链表头部。

8、最后,就是描述数据块随着 SQL 语句的执行不断地在 free 链表、flush 链表和 lru 链表中移动了。

6 常见问题

 写缓存(change buffer)

  对于读请求,缓冲池能够减少磁盘IO,提升性能。问题来了,那写请求呢?

情况一

  假如要修改页号为4的索引页,而这个页正好在缓冲池内。

 如上图序号1-2:

 (1)直接修改缓冲池中的页,一次内存操作;

 (2)写入redo log,一次磁盘顺序写操作;

 这样的效率是最高的。

画外音:像写日志这种顺序写,每秒几万次没问题。

 

是否会出现一致性问题呢?

 并不会。

  (1)读取,会命中缓冲池的页;

  (2)缓冲池LRU数据淘汰,会将“脏页”刷回磁盘;

  (3)数据库异常奔溃,能够从redo log中恢复数据;

 

什么时候缓冲池中的页,会刷到磁盘上呢?

  定期刷磁盘,而不是每次刷磁盘,能够降低磁盘IO,提升MySQL的性能。

画外音:批量写,是常见的优化手段。

 

情况二

 假如要修改页号为40的索引页,而这个页正好在缓冲池内。

 

此时麻烦一点,如上图需要1-3:

 (1)先把需要为40的索引页,从磁盘加载到缓冲池,一次磁盘随机读操作;

 (2)修改缓冲池中的页,一次内存操作;

 (3)写入redo log,一次磁盘顺序写操作;

 

 没有命中缓冲池的时候,至少产生一次磁盘IO,对于写多读少的业务场景,是否还有优化的空间呢?

 

 这即是InnoDB考虑的问题,又是本文将要讨论的写缓冲(change buffer)。

画外音:从名字容易看出,写缓冲是降低磁盘IO,提升数据库写性能的一种机制。

 

什么是InnoDB的写缓冲?

  在MySQL5.5之前,叫插入缓冲(insert buffer),只针对insert做了优化;现在对delete和update也有效,叫做写缓冲(change buffer)。

 

  它是一种应用在非唯一普通索引页(non-unique secondary index page)不在缓冲池中,对页进行了写操作,并不会立刻将磁盘页加载到缓冲池,而仅仅记录缓冲变更(buffer changes),等未来数据被读取时,再将数据合并(merge)恢复到缓冲池中的技术。写缓冲的目的是降低写操作的磁盘IO,提升数据库性能。

 

InnoDB加入写缓冲优化,上文“情况二”流程会有什么变化?

  假如要修改页号为40的索引页,而这个页正好在缓冲池内。

 

加入写缓冲优化后,流程优化为:

 (1)在写缓冲中记录这个操作,一次内存操作;

 (2)写入redo log,一次磁盘顺序写操作;

 其性能与,这个索引页在缓冲池中,相近。

画外音:可以看到,40这一页,并没有加载到缓冲池中。

 

是否会出现一致性问题呢?

 也不会。

  (1)数据库异常奔溃,能够从redo log中恢复数据;

  (2)写缓冲不只是一个内存结构,它也会被定期刷盘到写缓冲系统表空间;

  (3)数据读取时,有另外的流程,将数据合并到缓冲池;

 

 不妨设,稍后的一个时间,有请求查询索引页40的数据。

此时的流程如序号1-3:

  (1)载入索引页,缓冲池未命中,这次磁盘IO不可避免;

  (2)从写缓冲读取相关信息;

  (3)恢复索引页,放到缓冲池LRU里;

画外音:可以看到,40这一页,在真正被读取时,才会被加载到缓冲池中。

 

  还有一个遗漏问题,为什么写缓冲优化,仅适用于非唯一普通索引页呢?

  InnoDB里,聚集索引(clustered index)和普通索引(secondary index)的异同,《1分钟了解MyISAM与InnoDB的索引差异》有详尽的叙述,不再展开。

 

  如果索引设置了唯一(unique)属性,在进行修改操作时,InnoDB必须进行唯一性检查。也就是说,索引页即使不在缓冲池,磁盘上的页读取无法避免(否则怎么校验是否唯一?),此时就应该直接把相应的页放入缓冲池再进行修改,而不应该再整写缓冲这个幺蛾子。

 

 除了数据页被访问,还有哪些场景会触发刷写缓冲中的数据呢?

  还有这么几种情况,会刷写缓冲中的数据:

  (1)有一个后台线程,会认为数据库空闲时;

  (2)数据库缓冲池不够用时;

  (3)数据库正常关闭时;

  (4)redo log写满时;

画外音:几乎不会出现redo log写满,此时整个数据库处于无法写入的不可用状态。

 

什么业务场景,适合开启InnoDB的写缓冲机制?

 先说什么时候不适合,如上文分析,当:

  (1)数据库都是唯一索引;

  (2)或者,写入一个数据后,会立刻读取它;

 这两类场景,在写操作进行时(进行后),本来就要进行进行页读取,本来相应页面就要入缓冲池,此时写缓存反倒成了负担,增加了复杂度。

 

 什么时候适合使用写缓冲,如果:

  (1)数据库大部分是非唯一索引;

  (2)业务是写多读少,或者不是写后立刻读取;

 可以使用写缓冲,将原本每次写入都需要进行磁盘IO的SQL,优化定期批量写磁盘。

画外音:例如,账单流水业务。

 

上述原理,对应InnoDB里哪些参数?

 有两个比较重要的参数。

 

参数:innodb_change_buffer_max_size

介绍:配置写缓冲的大小,占整个缓冲池的比例,默认值是25%,最大值是50%。

画外音:写多读少的业务,才需要调大这个值,读多写少的业务,25%其实也多了。

 

参数:innodb_change_buffering

介绍:配置哪些写操作启用写缓冲,可以设置成all/none/inserts/deletes等。

 

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值