MySQL中读页缓冲区buffer pool详解

目录
  • Buffer pool
    • buffer pool组成
    • free链表
    • 缓存页的哈希处理
    • flush链表的管理
    • LRU链表
    • 刷新脏页
    • 多个buffer pool实例
    • 动态调整buffer pool大小
    • 查看buffer pool具体的信息

Buffer pool

我们都知道我们读取页面是需要将其从磁盘中读到内存中,然后等待CPU对数据进行处理。我们直到从磁盘中读取数据到内存的过程是十分慢的,所以我们读取的页面需要将其缓存起来,所以MySQL有这个buffer pool对页面进行缓存。

首先MySQL在启动时会向操作系统申请一段连续的内存空间,这一段空间就是作为buffer pool所用。将缓存的页放入buffer pool中管理起来。

mysql> show variables like 'innodb_buffer_pool_size';
+-------------------------+-----------+
| Variable_name           | Value     |
+-------------------------+-----------+
| innodb_buffer_pool_size | 134217728 |
+-------------------------+-----------+
1 row in set, 1 warning (0.00 sec)

我们可以看到默认是134217728字节,即128MB。一个页面是16KB,我们申请16KB倍数的缓存区大小就不会产生碎片。

buffer pool组成

同时呢,在buffer pool中还有包含每个页面的控制信息,即控制块。每个控制块对应管理每一个页面 (我们使用地址引用每一个页面) ,控制块用来存储页面的一些信息,控制块的占用大小不包括在innodb_buffer_pool_size中。由MySQL在启动时自己额外申请空间。

在控制块和缓存页中间会有部分碎片,就是空间无法全部利用的产生的碎片。因为MySQL向操作系统申请的内存空间需要申请一定大小的控制块空间,不能确定具体的大小,难免回有无法利用的空间。

free链表

free链表顾名思义,就是管理空闲的缓存页的链表,如果缓存页没有被使用,其控制块就会连接到free链表上。

通过一个基节点连接控制块形成一个free链表,并存储空闲页的数量等基本信息。

当我们从磁盘读取一个页到buffer pool中,就会取一个空闲的控制块填上对应缓存页的基本信息。

缓存页的哈希处理

MySQL在buffer pool中怎么快速存取一个页,以及查看对应页有没有被缓存到buffer pool中呢?

这就是用到哈希表,在Java中就是hashmap,通过表空间+页号做处理形成一个hash的key值,然后value值就是缓存页在buffer pool中的地址。

flush链表的管理

学习到这一章节的时候我震惊了,首先确实和我的理解是不一样的,以及到后面的MVCC确实让我大开眼界,这是我学习一遍后回头做的总结,所以比较言简意赅哈。

我们使用SQL语句对某条记录进行修改的时候,就会修改某个页面或者多个页面,我们对于页面的修改呢,并不会直接对磁盘进行对应的修改,因为对于磁盘IO实在是太慢了,我们首先会将修改的页面(简称脏页)链起来,就和free链表差不多,就是一个基节点将对应脏页的控制块连接在一起。

这个flush链表就代表我们即将还没有将页面更新到磁盘的链表。

LRU链表

因为buffer pool的大小是有限的,所以我们对于缓存页的大小是有限的,所以我们需要将不用的页面进行一个淘汰。MySQL采用的就是LRU的方式进行淘汰。

LRU就是最久未使用淘汰的策略,我们使用一个链表将缓存页面链起来,最近访问的出现在最前面,最久未访问的在链表末尾,当LRU满了新页面都进来机会淘汰链表尾部页面。

我们直接使用LRU,当MySQL进行预读或者全表扫描出现大量低频页面被读进LRU链表,会导致高频的页面直接被淘汰掉了,取而代之的是一些不经常用的页面。

预读就是MySQL优化器认为当前请求可能会读取的页面,预先将其加载到内存的buffer pool中。可以分为两种:

  • 线性预读

当读取一个区的页面超过系统变量innodb_read_ahead_threshold的值默认为56,也就是说当我们读取一个区的页面超过56页,MySQL就会异步的读取下一个区的所有页面到内存中。

  • 随机预读

如果buffer pool已经缓存了某个区的13个页面,不管是不是顺序的,只要有13页缓存了,就会触发MySQL异步读取本区的所有页面到MySQL中。我们可以控制关闭随机预读,也就是系统变量innodb_random_read_ahead。默认是OFF。

所以出现了改进基于分区的LRU链表,将链表分为两份。

一个是使用频率非常高的young区域,一个是使用频率不是很高的old区。

正常来说old区占比是37%,所以young区就占63%,我们可以通过innodb_old_blocks_pct来修改,默认就是37。

我们来讲讲这个基于分区的LRU链表。

  1. 首先buffer pool初始化,会将读取的页面直接放进old区。
  • 但是如果我们对于同一个页面的多条记录进行访问的话,我们就会多次访问同一页多次。但是如果我们是全表扫描的话,是可能会将所有页面缓存进缓存池中的,所以MySQL对于其进行优化。
  • 所以MySQL对于当页面第一次读入old区并在一定时间间隔(innodb_old_blocks_pct)内的多次访问来说是不会将其放入young区进行缓存的。innodb_old_blocks_pct的值默认为1000,就是刚来的来一秒内的多次访问是不会将其转移到young区的。
  1. 如果多次访问就会将old区的页升级到young区。当young区的页面被访问,只有young链表后1/4的页面被访问时才会将其转置到young区链表头,不然就不会改动,减少一些调整链表的性能损失。

刷新脏页

MySQL会启动后台线程进行脏页,也就是修改的页面进行刷新到磁盘。

以下有两种方式刷新脏页:

  • 从LRU的尾部扫描一些页面,刷新其中的脏页到磁盘中。

    • 后台线程会从LRU链表中old区域尾部,即不经常使用的页面中查找有没有脏页,有就更新到磁盘。可以更改系统变量innodb_lru_scan_depth来控制扫描区域尾部的数量。
  • 从flush链表中更新到磁盘。
    • 我们上面说了flush连接这脏页的控制块,我们就可以将连接这flush链表的脏页进行更新。

疑问:为什么要两种方式更新呢?我刚开始不懂这是我回过头来看的时候就懂了

首先我们脏页是缓存在buffer pool中的,但是我们buffer pool空间是有限的,又因为我们使用的是LRU的方式,又因为从flush链表将脏页同步到磁盘效率实在不高,所以不会很经常去更新脏页。如果我们不更新直接将其从LRU的链表抛弃也就是从缓存池中直接扔了,但是它是脏页就无法同步到磁盘了,同时flush链表链接的也会出现问题。

所以在LRU淘汰很久未使用的页有个前提就是它不是一个脏页。所以我们会去检测LRU链表尾部有没有脏页,然后更新它,我们才能去淘汰掉这些页。

flush链表更新那就是它的本职工作了,它存这个也是干这个的,应该没有什么问题。

当系统十分繁忙,buffer pool使用量不足的时候,因为磁盘IO太慢了,所以会出现一种情况,就是大量的用户线程也在进行这个同步脏页的活。不同步脏页然后淘汰buffer pool的页面,没法读取页面啊。

多个buffer pool实例

我们可以设置多个buffer pool来实现多实例提高性能。

mysql> show variables like 'innodb_buffer_pool_instances';
+------------------------------+-------+
| Variable_name                | Value |
+------------------------------+-------+
| innodb_buffer_pool_instances | 1     |
+------------------------------+-------+
1 row in set, 1 warning (0.00 sec)

我们可以设置innodb_buffer_pool_instances系统变量来控制实例变量。

但是当buffer pool的大小小于1G的时候,设置2个实例也是没有用的(会被恢复成1个),多实例的情况是建立在大内存的情况下的。

动态调整buffer pool大小

在MySQL5.7.5后,MySQL中的buffer pool的大小是以chunk来分配了,如下图。

一个buffer pool是由多个chunk组成的,所以MySQL向操作系统申请连续的内存空间,就是以chunk的方式来申请的,这样我们可以在MySQL运行时调整buffer pool的大小。但是chunk的大小是不能在运行时更改的,这样是很耗费性能的。?

innodb_buffer_pool_size / innodb_buffer_pool_instances = 每个实例buffer pool的大小。

每个实例的大小 / innodb_buffer_pool_chunk_size = 每个实例由多少个chunk构成。

不是弄很明白,怎么动态调整大小,我调整了但是mysqld占用内存大小还是只能重启才能生效,我不会。

查看buffer pool具体的信息

show engine innodb status;

到此这篇关于MySQL中读页缓冲区buffer pool的文章就介绍到这了,更多相关MySQL buffer pool内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • MySQL的查询缓存和Buffer Pool

    一.Caches - 查询缓存 下图是MySQL官网给出的:MySQL架构体系图. 人们常说的查询缓存就是下图中的Cache部分. 如果将MySQL分成 Server层和存储引擎层两大部分,那么Caches位于Server层. 另外你还得知道: 当一个SQL打向MySQL Server之后,MySQL Server首选会从查询缓存中查看是否曾经执行过这个SQL,如果曾经执行过的话,之前执行的查询结果会以Key-Value的形式保存在查询缓存中.key是SQL语句,value是查询结果.我们将这个

  • 深入理解MySQL中MVCC与BufferPool缓存机制

    目录 一.MVCC机制 undo日志版本链与read-view机制 版本链比对规则 二.BufferPool机制 三.总结 一.MVCC机制 MVCC(Multi Version Concurrency Control),MySQL(默认)RR隔离级别就是通过该机制来保证的,对一行数据的读与写两个操作默认是不会通过加锁互斥来保证隔离性的 串行化隔离级别是为了保证较高的隔离性,是通过将所有操作加锁互斥来实现的 MySQL在RC隔离级别和RR隔离级别下都实现了MVCC机制 RC每次查询都会创建一个r

  • mysql的Buffer Pool存储及原理

    一.前言 1.buffer pool是什么 咱们在使用mysql的时候,比如很简单的select * from table;这条语句,具体查询数据其实是在存储引擎中实现的,数据库中的数据实际上最终都是要存放在磁盘文件上的,如果每次查询都直接从磁盘里面查询,这样势必会很影响性能,所以一定是先把数据从磁盘中取出,然后放在内存中,下次查询直接从内存中来取.但是一台机器中往往不是只有mysql一个进程在运行的,很多个进程都需要使用内存,所以mysql中会有一个专门的内存区域来处理这些数据,这个专门为my

  • 详解MySQL中的缓冲池(buffer pool)

    Mysql 中数据是要落盘的,这点大家都知道.读写磁盘速度是很慢的,尤其和内存比起来更是没的说.但是,我们平时在执行 SQL 时,无论写操作还是读操作都能很快得到结果,并没有预想中的那么慢. 可能你会说我有索引啊,有索引当然快了.但是铁子,索引文件也是存储在磁盘上的,查找过程会产生磁盘 I/O.如果同时对某行数据进行多次操作,那岂不是要重复产生很多次磁盘 IO 吗? 可能你想到了,那我把数据存在内存里不就可以了吗?内存速度比磁盘快,这准没毛病.没错,那该怎么存呢? 这就是我们今天所要讲的主题--

  • mysqldump造成Buffer Pool污染的研究

    前言: 最近Oracle MySQL在其官方Blog上贴出了 5.6中一些变量默认值的修改.其中innodb_old_blocks_time 的默认值从0替换成了1000(即1s) 关于该参数的作用摘录如下: how long in milliseconds (ms) a block inserted into the old sublist must stay there after its first access before it can be moved to the new subl

  • MySQL中读页缓冲区buffer pool详解

    目录 Buffer pool buffer pool组成 free链表 缓存页的哈希处理 flush链表的管理 LRU链表 刷新脏页 多个buffer pool实例 动态调整buffer pool大小 查看buffer pool具体的信息 Buffer pool 我们都知道我们读取页面是需要将其从磁盘中读到内存中,然后等待CPU对数据进行处理.我们直到从磁盘中读取数据到内存的过程是十分慢的,所以我们读取的页面需要将其缓存起来,所以MySQL有这个buffer pool对页面进行缓存. 首先MyS

  • mysql中decimal数据类型小数位填充问题详解

    前言 在开发过程中,我们往往会用到decimal数据类型.因为decimal是MySQL中存在的精准数据类型. MySQL中的数据类型有:float,double等非精准数据类型和decimal这种精准. 区别:float,double等非精准类型,在DB中保存的是近似值. Decimal则以字符串的形式保存精确的原始数值. decimal介绍: decimal(a,b) 其中:a指定指定小数点左边和右边可以存储的十进制数字的最大个数,最大精度38.b指定小数点右边可以存储的十进制数字的最大个数

  • MySQL中replace into与replace区别详解

    目录 0.故事的背景 1.replace into 的使用方法 2.有唯一索引时—replace into & 与replace 效果 3.没有唯一索引时—replace into 与 replace 1).replace函数的具体情况 2).replace into 函数的具体情况 4.replace的用法 本篇为抛砖引玉篇,之前没关注过replace into 与replace 的区别.经过多个场景测试,居然没找到在插入数据的时候两者有什么本质的区别?如果了解详情的伙伴们,请告知留言告知一二

  • Mysql中事务ACID的实现原理详解

    引言 照例,我们先来一个场景~ 面试官:"知道事务的四大特性么?" 你:"懂,ACID嘛,原子性(Atomicity).一致性(Consistency).隔离性(Isolation).持久性(Durability)!" 面试官:"你们是用mysql数据库吧,能简单说说innodb中怎么实现这四大特性的么?" 你:"我只知道隔离性是怎么做的balabala~~" 面试官:"还是回去等通知吧~" OK,回到正题

  • MySQL中的行级锁定示例详解

    前言 锁是在执行多线程时用于强行限定资源访问的同步机制,数据库锁根据锁的粒度可分为行级锁,表级锁和页级锁 行级锁 行级锁是mysql中粒度最细的一种锁机制,表示只对当前所操作的行进行加锁,行级锁发生冲突的概率很低,其粒度最小,但是加锁的代价最大.行级锁分为共享锁和排他锁. 特点: 开销大,加锁慢,会出现死锁:锁定粒度最小,发生锁冲突的概率最大,并发性也高: 实现原理: InnoDB行锁是通过给索引项加锁来实现的,这一点mysql和oracle不同,后者是通过在数据库中对相应的数据行加锁来实现的,

  • 关于MySQL中的查询开销查看方法详解

    MySQL逻辑架构 如果能在头脑中构建一幅MySQL各组件之间如何协同工作的架构图,有助于深入理解MySQL服务器.下图展示了MySQL的逻辑架构图. MySQL逻辑架构,来自:高性能MySQL MySQL逻辑架构整体分为三层,最上层为客户端层,并非MySQL所独有,诸如:连接处理.授权认证.安全等功能均在这一层处理. MySQL大多数核心服务均在中间这一层,包括查询解析.分析.优化.缓存.内置函数(比如:时间.数学.加密等函数).所有的跨存储引擎的功能也在这一层实现:存储过程.触发器.视图等.

  • MySQL中replace into语句的用法详解

    在向表中插入数据的时候,经常遇到这样的情况: 1.首先判断数据是否存在: 2.如果不存在,则插入: 3.如果存在,则更新.   在 SQL Server 中可以这样写: 复制代码 代码如下: if not exists (select 1 from table where id = 1) insert into table(id, update_time) values(1, getdate()) else update table set update_time = getdate() whe

  • MySQL中的唯一性约束与NULL详解

    前言 之前做的一个需求,简化描述下就是接受其他组的 MQ 的消息,然后在数据库里插入一条记录.为了防止他们重复发消息,插入多条重复记录,所以在表中的几个列上加了个唯一性索引. CREATE UNIQUE INDEX IDX_UN_LOAN_PLAN_APP ON testTable (A, B, C); 这时 A,B,C 三列都是不允许 NULL 值的,唯一性约束也是 work 的. 后来由于需求的变化,修改了以前的唯一性约束,又多加了一列.(至于为什么加就不赘述了). ALTER TABLE

  • MySQL中日期型单行函数代码详解

    MySQL中日期型单行函数: CURDATE()或CURRENT_DATE() 返回当前日期(精确到日) 参考代码如下: SELECT CURDATE() FROM DUAL; SELECT CURRENT_DATE() FROM DUAL; -----------------------------------(分割线)------------------------------------ CURTIME() 或CURRENT_TIME() 返回当前时间(精确到秒) 参考代码如下: SELE

随机推荐