赞
踩
MySQL作为一个存储系统,为了提高性能,减少磁盘io,设计了缓冲池机制(buffer pool),innodb的存储结构组成如下图:
buffer pool作为innodb内存结构的四大组件之一,不属于mysql的server层,是innodb存储引擎的缓冲池。
buffer pool是一块内存区域,是一种“降低磁盘访问机制”,buffer pool缓存数据表和索引数据,吧磁盘上的数据加载到缓冲池,避免每次访问都进行磁盘IO,起到加速访问的作用。buffer pool也是以页为存储单位(与磁盘中数据页索引页单位一样,默认大小16K),buffer pool底层采用链表的数据结构管理page。
所有数据页的读写操作都要通过buffer pool进行
写操作的事务持久性由redo log落盘保证,buffer pool只是为了提高读写效率。
buffer pool中缓存的是数据页,数据页大小和磁盘默认数据页大小一样,为了更好的管理缓存页,buffer pool有个“描述数据的区域”:
innodb为每一个缓存的数据页都创建了一个单独的区域,记录数据页的元数据信息,包括页所属的表空间,数据页编号,缓存页在buffer pool中的地址,链表节点信息,锁信息以及LSN信息等,成为控制块。
控制块和缓存页一一对应,都是存在buffer pool中,控制块大概占缓存页的5%左右。
buffer pool里面有三个链表,LRU链表,free链表,flush链表,innodb就是通过这三个链表的使用来控制管理数据页。
1.3.1 buffer pool 的初始化
启动mysql服务器的时候,需要完成对buffer pool的初始化过程,即分配buffer pool空间,划分为若干控制块和缓存页。
划分空间后buffer pool中缓存页都是空的,后续对数据操作时,才会从磁盘中读出数据存放在buffer pool。
1.3.2 free链表
free链表即空闲链表,解决的问题就是从磁盘读取数据页到buffer pool时,如何判断buffer pool 中哪些数据页是空的,可以用来缓存数据。
free链表是一个双向链表,由一个基节点和若干子节点组成,记录空闲的数据页对应的控制块信息。
通过free链表,可以知道buffer pool中哪些缓存页可以用来缓存数据。那么如何确定数据页是否需要被缓存呢?(即如何知道数据页已经在buffer pool中了,不需要在去磁盘读取了)
数据库提供了一个数据页缓存哈希表,表空间号+数据页号 作为key,缓存页控制块地址为value。使用数据页时,先在数据页哈希表中去查找,如果找到了,直接根据value定位控制块,然后找缓存页,如果没有找到,则读取磁盘数据页写入缓存,然后在加入哈希表。
1.3.3 LRU链表。
首先考虑两个问题:
这两个问题是借助LRU链表解决的:
buffer pool作为innodb自带的缓冲池,读写都是在其中进行的,但是buffer pool的大小是有限制的,因此希望频繁访问的数据可以长时间留在buffer pool中,而访问较少的数据,能够优先释放掉为其他数据页留出缓存空间。基于这个目的,采用了LRU链表 ---> 频繁访问的数据放在链表头部,不怎么访问的数据放在链表末尾,空间不够的时候就从尾部开始淘汰,从而为其他数据腾出空间。LRU链表本质也是由控制块组成。
传统的LRU会有一定的问题:假如执行了一次全表扫描 select * 。。。 这样会导致大量的数据加载到buffer pool里面,从而把原本的热数据被淘汰,下次访问,又需要重新读取磁盘。因此mysql对LRU进行了改进:
每次有数据被加载到buffer pool后,先插入到old区域的头部,并标记第一次访问的时间,后续如果在访问到当前页,并且访问时间间隔大于设置的间隔时间innodb_old_blocks_time(默认1s),才会把当前页给提高到young区域。通过这种方式,就避免了全部扫描导致的只访问一次的数据页覆盖掉热点数据的问题。
此外,因为热点区域的数据被频繁访问,如果young区域每次访问某一页,就把当前页移动到young区域的head,会导致LRU链表频繁的变形,因此mysql又做了一个优化:young区域前1/4被访问不会被移动到头部,只有后面的3/4被访问才会移动到头部。
1.3.4 flush链表
flush链表和free链表结构很类似,也是由基节点和子节点租出的双向链表,链表节点时被修改过的缓存页对应的控制块。
flush链表的作用是:帮助定位脏页,即需要刷盘的缓存页,当控制块被加入到flush链表后,后台线程就可以遍历flush链表,将脏页写入磁盘。 ---- 但是这只是被更新数据已经加载到buffer pool的前提,如果没有预先加载,则先记录在change buffer中。
1.3.5 buffer pool中的数据页
作用:优化每次更新操作之后都写入redo log而产生的磁盘IO问题。
当buffer pool中某页的数据更改后,这个页就变成了脏页(因为mysql为了提升性能,避免频繁的随机IO,每次更改数据不是第一时间去更改磁盘数据,这就导致了buffer pool中的数据和磁盘数据不一致)
为了保证脏页落盘的安全性,防止断电丢失等,会预写redo log日志(磁盘中)。---随机IO变成顺序IO,效率提升
但是如果每次更新操作都写入redo log日志,虽然是顺序IO,但是频繁IO也影响性能,因此在内存中开辟了一个log buffer区域,用于缓存要写入redo log的数据,在事务提交的时候将log buffer里面的数据进行持久化。----(mysql默认的策略,但是也可以更改)
innodb_flush_log_at_trx_commit 参数
Log buffer的持久化策略如下,可以通过参数进行调节 innodb_flush_log_at_trx_commit 进行调节:
在MySQL5.5之前,叫插入缓冲(Insert Buffer),只针对INSERT做了优化;现在对DELETE和UPDATE也有效,叫做写缓冲(Change Buffer)。
它是一种应用在非唯一普通索引页(non-unique secondary index page)不在缓冲池中,对页进行了写操作,并不会立刻将磁盘页加载到缓冲池,而仅仅记录缓冲变更(Buffer Changes),等未来数据被读取时,再将数据合并(Merge)恢复到缓冲池中的技术。写缓冲的目的是降低写操作的磁盘IO,提升数据库性能。
1、mysql的写操作如何减少IO次数?--- 引出change buffer
mysql的读请求可以通过缓冲池减少与磁盘IO,提示性能,那么写请求是否也可以呢?
情况1:要修改的页4刚好在缓冲池内:
处理过程
- 直接修改缓冲池中的页,一次内存操作
- 写入redo log日志,一次磁盘顺序操作
是否会有一致性问题?
不会
- 读取,直接命中缓冲池中的页
- 缓冲池的LRU数据淘汰,会将脏页刷回磁盘
- 数据库异常崩溃,从redo log中恢复数据
情况2:要修改的页不在缓冲池内:
可以先把要修改的页读到缓冲池,然后执行情况1操作,而change buffer就是为了优化这种场景。
处理过程:
- change buffer中记录这个写操作,一次内存操作
- 写入redo log,一次磁盘顺序操作。
是否会有一致性问题?
不会
- 数据库异常崩溃,能从redo log中恢复数据
- changebuffer 会定期刷盘,到 写缓冲系统表空间
- 数据读取有另外的流程,将数据合并到缓冲池。
2、后续读取change buffer 涉及到的页数据操作?
上述的场景2中,通过change buffer修改了没在缓冲池中的页,现在要读取这个页,操作如下:
- 查询,缓冲池未命中,从磁盘拷贝这个页 (不可避免)
- 从change buffer中读取这个页的相关信息
- 数据合并(merge),存至缓冲池中。
3、change buffer 只适用于非唯一普通索引页?
如果索引设置了唯一属性,在进行修改操作的时候,innodb必须进行唯一性检查,也就是说,即便索引页不在缓冲池中,磁盘的读取也是不可避免的。此时就应该直接把相应的页放入缓冲池然后进行修改,而没有必要进行“写缓冲”这多余操作。
4、change buffer中数据刷新的触发时机?
5、change buffer适用场景?
总结
1、Change Buffer是一种特殊的内存结构,它是一种应用在非唯一普通索引页(non-unique secondary index page)不在缓冲池中,对页进行了写操作,并不会立刻将磁盘页加载到缓冲池,而仅仅记录缓冲变更(Buffer Changes),等未来数据被读取时,再将数据合并(Merge)恢复到缓冲池中的技术。写缓冲的目的是降低写操作的磁盘IO,提升数据库性能;
2、如果索引设置了唯一(Unique)属性,在进行修改操作时,InnoDB必须进行唯一性检查,是不能使用到Change Buffer的;
3、根据业务使用情况,可以指定Change Buffer占整个缓冲池的比例。同时,也可以指定配置哪些写操作启用写缓冲,可以设置成all/none/inserts/deletes等;
4、Change Buffer不会出现数据不一致的情况,原因有二:
数据库异常奔溃,能够从redo log中恢复数据;
写缓冲不只是一个内存结构,它也会被定期刷盘到写缓冲系统表空间。
一般情况下,Linux文件系统的页大小是4KB,而我们知道 mysql的页大小为16KB(oracle为8KB),这就说明,mysql将buffer pool中的一页数据刷到磁盘里面,需要写4个文件系统页。但是这个操作并不是原子性的,如果写到一半断电,就会发生“页数据损坏”。
redo log无法修复这类页数据损坏异常。(修复的前提是页数据正确并且redo日志正常。)
double write buffer 和传统的buffer不同,它分为内存和磁盘两层架构。工作流程如下:当有页数据要刷盘时:
doublewrite buffer 是如何解决页数据损坏问题?
两次磁盘写会不会影响性能?
总结:
- 1个MySQL的页(Page)可以映射4个操作系统(文件系统)的页(OS Page),Doublewrite Buffer是为了保证因系统页损坏导致的MySQL数据丢失的保证方案;
- Doublewrite Buffer是内存+磁盘结构,内存结构是由128个页组成,大小为16KB × 128 = 2MB,“Double”的由来是因为数据写两次磁盘:一次是写DWB的磁盘结构(此过程分两次写入,每次写入16KB × 64 = 1MB的数据),一次是直接写入Data File(.ibd);
- MySQL 8.0较MySQL 5.6、5.7版本的Doublewrite Buffer产生了一些新的变化,DWB磁盘结构的数据存放从共享系统表空间中分离出来,存放在单独的.dblwr文件中;
- MySQL 8.0新增了一些涉及Doublewrite Buffer的参数,可以更细粒度的管理Doublewrite Buffer,但新增参数多用于高级性能调整,我们使用默认值即可,无需更多关注。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。