InnoDB(2.1):体系架构

InnoDB存储引擎

InnoDB存储引擎的版本

InnoDB各版本功能对比

版本功能
老版本InnoDB支持ACID(事务)、行锁设计、MVCC
InnoDB 1.0.X继承了上述版本所有功能,增加了compress和dynamic页格式
InnoDB 1.1.X继承了上述版本所有功能,增加了Linux AIO、多回滚段
InnoDB 1.2.X继承了上述版本所有功能,增加了全文索引支持,在线索引添加

下面说说InnoDB 1.0.X和InnoDB 1.1.X的不同之处,虽然InnoDB 1.0.X增加了compress和dynamic页格式,但是InnoDB Plugin是不支持Linux AIO、多回滚段,导致了InnoDB Plugin支持的最大支持并发事务数量也被限制在1023。

InnoDB体系结构
  • 内存池(由多个内存块组成,可以认为这些内存块组成了一个大的内存池,并且负责以下工作)
    • 维护所有进程/线程需要访问的多个内部数据结构
    • 缓存磁盘上的数据,方便快速地读取,同时在对磁盘文件的数据修改之前在这里缓存
    • 重做日志(redo log)缓冲
    • 。。。
  • 后台进程(主要作用是负责刷新内存池中的数据,保证缓冲池中的内存缓存的是最近的数据,此外还要哦将已经修改的数据文件刷新到磁盘文件,同时保证在数据库发生异常的情况下InnoDB能恢复到正常运行状态)
后台线程

InnoDB存储引擎是多线程的模型,因此其后台有多个不同的后台线程,负责处理不同的任务。

Master Thread

Master Thread是一个非常核心的后台线程,主要负责将缓冲池中的数据异步刷新到磁盘中,保证数据的一致性,包括脏页的刷新、合并插入缓冲(Insert Buffer)、UNDO页的回收等。

IO Thread

在InnoDB存储引擎中大量使用了AIO(Async IO,异步执行IO)来处理写IO请求,这样可以极大提高数据库的性能,而IO Thread的工作主要是负责这些IO请求的回调处理(call back,主要负责IO请求))。InnoDB 1.0版本之前共有4个IO Thread,分别是**write、read、insert buffer和log IO thread。**在Linux平台下,IO Thread的数量不能进行调整,但是在windows平台下,可以通过参数innodb_file_io_threads来增大IO Thread。从InnoDB 1.0.X版本开始,read thread和write thread分别增加到了4个(即read和write的线程分别有4个总共有8个,其他的log和insert buff还是一个),并且不能再使用innodb_file_io_threads参数来修改IO Thread数量,而是分别使用下面的参数来进行修改,innodb_read_io_threads和innodb_write_io_threads参数进行设置。

我们可以通过下面SQL查看InnoDB版本和线程

SHOW VARIABLES LIKE 'innodb_version';
SHOW VARIABLES LIKE 'innodb_%io_threads%';

在这里插入图片描述
我们也可以通过下面SQL查看所有线程数量

SHOW ENGINE INNODB STATUS;

在这里插入图片描述
可以看到除了thread 0为insert buffer thread和thread 1为log thread外,其他都是read和write thread,也可以很清楚看到,insert buff thread和log thread都只有一个,read thread和write thread都有4个。

也就是说,innodb各花上4个线程去执行读写,对于日志记录和缓冲池插入都分别只有1个。

Purge Thread

当事务被提交后,其所使用的undolog页可能不再需要,因此需要PurgeThread来回收已经使用并分配的undo页,在InnoDB1.1版本之前,Purge操作仅在InnoDB存储引擎的Master Thread中完成,而在InnoDB1.1版本之后,purge操作可以独立到单独的线程中执行,也就是在Purge Thread中执行,以此来减轻Master Thread的工作,从而提高CPU的使用以及提升存储引擎的性能

InnoDB1.2版本之前,只能开启一个Purge Thread,在1.2之后,支持多个Purge Thread,这样做的目的是为了进一步加快undo页的回收,同时由于Purge Thread需要离散地读取undo页,这样也能更进一步利用磁盘的随机读取性能

内存
缓冲池

InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可将其视为基于磁盘的数据库系统,这种数据库系统,速度会受限于CPU和磁盘,所以在软件层面上进行优化大部分都是通过使用缓冲池技术来提高数据库的整体性能。

缓冲池简单来说就是一块内存区域,就是使用了内存读速度快的特点弥补了磁盘读速度慢,在数据库中进行读取页的操作,首先将磁盘读取到的页存放在缓冲池中,这个过程称为将页"FIX"在缓冲池中,下一次再读取相同的页时,会先去缓冲池中找,找到了就直接从缓冲池中读取,然后再读取磁盘上的页。

修改上,也是会提前修改缓冲池中的页,然后再以一定的频率刷新到磁盘上(修改前是先要将数据读出来,找到想到修改的地方,所以缓冲池会有页)。

但是这里要注意的是,对于缓冲池刷新回磁盘的操作并不是在每次页发生更新时就会触发,而是通过一种称为Checkpoint的机制刷新回磁盘(下面会说),这也是为了可以提高数据库的整体性能。

对于InnoDB存储引擎来说的话,缓冲池的配置是可以通过参数innodb_buffer_pool_size来设置的

//查看缓冲池
SHOW VARIABLES LIKE 'innodb_buffer_pool_size';

具体来看,缓冲池中缓存的数据页类型有:索引页(index)、数据页(聚集索引页的叶子结点)、undo页、插入缓冲(Insert buffer)、自适应哈希索引(adaptive hash index)、InnoDB存储的锁信息(lock info)、数据字典信息(data dictionary)等,不可以简单以为缓冲页里面只有索引页和数据页,这两部分只是占了缓冲池大部分而已。

在这里插入图片描述
InnoDB从1.0版本开始就允许有多个缓冲池实例。每个页根据哈希值会重新分配到不同缓冲池实例中,这样做的好处是减少数据库内部的资源竞争,同时增加数据库对并发的处理能力。缓冲池的实例也可以通过修改参数innodb_buffer_pool_instances来进行修改(该值默认为1)

//查看缓冲池实例
SHOW VARIABLES LIKE 'innodb_buffer_pool_instances';
LRU LIST、Free LIST和Flush List

缓冲池是一个很大的内存区域,其中存放各种类型的页,需要一系列的管理操作。

一般来说,数据库中的缓冲池是通过LRU算法(Latest Recent Used)来进行管理的,即最频繁使用的页会放在LRU列表中的前端,而最少使用的页会放在尾端,当缓冲池不能存放新读取到的页时,会先释放尾端的页,也就是不频繁使用的页。

在InnoDB引擎中,缓冲池中的页默认为16KB,同样使用LRU算法来对缓冲池进行管理,稍有不同的是,InnoDB存储引擎对传统的LRU算法做了一些优化,在InnoDB的存储引擎中,对于LRU列表还有一个midpoint值,新访问的页,不是直接放在LRU列表的前端,而是放在列表中的midpoint位置上,默认情况下,midpoint位置在LRU列表长度的八分之五,midpoint也是可以控制的,通过参数innodb_old_blocks_pct来控制。

//查看midpoint值
SHOW VARIABLES LIKE 'innodb_old_blocks_pct';

在这里插入图片描述
可以考到参数innodb_old_blocks_pct默认值为37,也就是37%(差不多八分之三的的位置,这时从尾端开始的37%,也就代表着热点数据大概占据63%),表示新读取的页会被放在列表大概八分之三的位置,在InnoDB中,把midPoint之后的列表称为old列表(不常用的,少用的),前面的称为new列表(常用的和新加入的)。

不直接将读取的页放入LRU前端是有好处的,因为可能存在SQL会进行大量扫描表,从而会产生大量第一次访问的数据,那么缓冲池就要进行替换,如果放入前端,很有可能将热点的数据被不是热点的数据替换掉,再次访问热点数据时,因为缓冲池上没有,又要去磁盘上进行读取,然后又放入缓冲区,大大降低了效率。midpoint很好地解决了这个问题,保证了最前端那些热点数据不被替换掉。

注意,为了解决上面热点数据被替换掉的问题,InnoDB还有一个参数来进一步管理LRU列表,这个参数是innodb_old_blocks_time,用于表示新的页读取到了缓冲区时,且被放入LRU列表中的midpoint位置后需要等待多久才会被加入到LRU列表的热端(也就是前端)

可以通过延缓新插入的页转移到前端的时间和设置热点数据页占LRU列表的百分比来减少热点页被刷出的可能性。

//设置新插入页在LRU列表位置后,需要等待1000s才能进入热端
SET GLOBAL innodb_old_blocks_time = 1000;
//设置新插入的页放在LRU列表的20%处,也就是让前端数据页占80%
SET GLOBAL innodb_old_blocks_pct = 20;

LRU列表用来管理已经读取过的页,但是当数据库刚启动时,LRU列表是空的,即里面没有任何的页,此时所有的页都是存储在Free列中的,当需要从缓冲池中插入页时,要进行一个分页操作(这是为了保证缓冲池中的页数要守恒),首先会从Free列表中找是否有空闲页,如果有的话,就直接从Free列表中删除这个空闲页,然后将对应的页放入到LRU列表中,如果没有空闲页,那就只能考LRU列表自己管理了,根据LRU算法,会先淘汰掉LRU列表末尾的页,然后将剩出的内存空间分配给新的页。(都是为了保证页数量守恒)

那么这里又会出现两种情况,第一种为新的页插入在midpoint位置后成为了热点页,从old部分移动到了new部分,这种行为成为page made young第二种就是新的页在插入midpoint位置后,由于innodb_old_blocks_time的设置未能成为热点页,这种成为page not made young

//查询innodb状态
SHOW ENGINE INNODB STATUS;

在这里插入图片描述
Buffer pool size指的就是缓冲池中有的页数,Free buffers就是Free列表中拥有的页数,Database pages就是LRU列表中的页数,Old database pages就是LRU列表中的old区,容易出现的情况是,Free buffers+Database pages不等于Buffer pool size,很有可能是因为缓冲池中的页会分配给缓冲池里的其他信息,比如锁信息,字典数据信息,自适应哈希索引信息,Insert buffer等,这一部分不在LRU算法维护的范围内。

然后还有三个重要的数据

  • Pages made young:代表新插入的页放到了前端的数量
  • not young:代表新插入的页未能放到前端的数量
  • Buffer pool hit rate:这里的话,我这里显示了no buffer pool …,是因为太久没访问了,这个参数代表命中率,也就是直接从缓冲池中进行读取页,不用访问磁盘页,如果发生Buffer pool hit rate低于95%的话(表示热点数据发生比较严重的替换),用户需要观察是否由于发生全表扫描引起的LRU列表被污染的问题。

从MySQL的1.2版本开始,可以通过表INNODB_BUFFER_POOL_STATS来观察缓冲池的运行状态(这是information_scheme架构里面的表)

SELECT POOL_ID,HIT_RATE,PAGES_MADE_YOUNG,PAGES_NOT_MADE_YOUNG FROM information_schema.`INNODB_BUFFER_POOL_STATS`;

在这里插入图片描述
此外还可以通过INNODB_BUFFER_PAGE_LRU表来观察每个LRU列表中每个页的具体信息

SELECT TABLE_NAME,SPACE,PAGE_NUMBER,PAGE_TYPE FROM information_schema.`INNODB_BUFFER_PAGE_LRU`;

InnoDB支持压缩页的功能,即将原来16KB的页可以压缩到为1KB、2KB、4KB和8KB,也是由于页的大小发生了变化,LRU列表也有了些许的改变,对于非16KB的页,是通过unzip_LRU列表进行管理的,而不是LRU列表,不过这里要注意的是LRU列表中的页是包括unzip_LRU列表里面的页的

现在就出现问题,页可以被压缩到不同的KB,那么unzip_LRU是怎么进行管理的呢?怎么从缓冲池中分配内存的呢?

首先,unzip_LRU对不同大小的压缩页是分别进行管理的(也就是4KB的有一个unzip_LRU列表,8KB的有一个unzip_LRU列表,16KB的又有一个unzip_LRU列表),然后分配内存是通过伙伴算法去进行内存分配的。

比如现在需要从缓冲池中申请分页,申请页为4KB大小的,过程如下

  • 首先,会先去检查4KB的unzip_LRU列表,检查是否有可用空间,如果有就直接使用
  • 如果4KB的unzip_LRU列表没有空间,然后去检查8KB的unzip_LRU列表,如果有,就将其分成2个4KB的页,都存放到对应的unzip_LRU列表中。

同样,我们可以从INNODB_BUFFER_PAGE_LAU表中去管擦unzip_LRU列表中的页

在LRU列表中的页被修改后,这个页会被称为脏页脏页表示缓冲池上的页与磁盘中的页的数据不一致了。这时数据库会采用CHECKPOINT机制将脏页刷新回磁盘上Flush列表中的页就是脏页,Flush也被称为脏页列表,这里需要注意的是,脏页不仅存在于Flush列表中,也存在LRU列表中,但这两者是不会互相影响的,LRU列表管理缓冲池中的页的可用性,而Flush列表用来管理脏页刷新回磁盘

//查看Flush列表(Modified db pages代表的就是脏页数量)
SHOW ENGINE INNODB STATUS;
//通过INNODB_BUFFER_PAGE_LRU来查看,不过要加WHERE OLDEST_MODIFICATION大于0
SELECT ... FROM INNODB_BUFFER_PAGE_LRU WHERE OLDEST_MODIFICATION > 0;
重做日志缓冲

InnoDB除了有缓冲池外,还有一个名为重做日志缓冲(redo log buffer)。InnoDB存储引擎首先将重做的日志信息放在这个缓冲区,然后按一定频率对其进行刷新到日志文件中。一半情况下,每一秒钟都会将重做日志刷新到日志文件,因此用户只需要保证每秒产生的事务量在这个缓冲大小即可(如果每秒的事务量超过了重做日志缓冲区大小,那么就很有可能出现日志缺失,因为缓冲区放不下),当然也可以手动扩展重做日志缓冲区大小,通过配置参数innodb_log_buffer_size控制,现在默认为16MB,老版本是8MB。

SHOW VARIABLES LIKE "innodb_log_buffer_size";

重做日志缓冲在下面三种情况下将会重做日志缓冲中的内容刷新到外部磁盘的重做日志文件中

  1. Master Thread每一秒将重做日志缓冲刷新到重做日志文件中
  2. 每个事务提交时会重新重做日志缓冲刷新到重做日志文件
  3. 当重做日志缓冲池剩余空间小于二分之一时,将重做缓冲缓冲池中的重做日志缓冲刷新到重做日志文件。
额外的内存池

在InnoDB存储引擎中,对内存的管理是通过一种称为内存堆的方式进行的,在对一些数据结构进行分配内存时,会先从额外的内存池中进行申请,当额外的内存池不够时,才会从缓冲池中去申请,例如,分配了缓冲池后,缓冲池里面的对象,每个缓冲池有自己对应的帧缓冲和对应的缓冲控制对象(并不是存储在缓冲池中),这些对象内存的申请是在额外的内存池中申请的,因此在申请了很大的InnoDB缓冲池时,也应该考虑相应地增加额外内存池的大小,否则会不够地方放缓冲池的一些对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值