在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个。
当事务被提交后,其所使用的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算法(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列表中
【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
的页是包括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列表中。