BufferPool的理解
由于BufferPool属于内存,在mysql中处于一个比较重要的角色,基本的操作其实都是基于BufferPool缓存进行的,而内存资源又是比较稀缺的所以对于BufferPool的内存管理是比较重要的,Mysql的内存管理主要如下
相当于对于bufferpool的管理主要有三个
- 1、空闲链表法
- 2、脏页链表管理
- 3、改进后的LRU算法来将热点数据缓存在BufferPool中
一、关于 Buffer Pool 的产生原因
Buffer Pool 的产生确实是为了加快 MySQL 的查询和更新操作。由于从磁盘加载数据相对缓慢,开辟连续的内存空间作为 Buffer Pool,可以在访问数据时先查看内存中是否有对应的数据,若有则直接从内存操作,没有则从磁盘加载到 Buffer Pool 后再进行操作,从而提高执行速度。
二、关于 Buffer Pool 的管理
空闲区域管理:
采用空闲列表法是正确的,将所有空闲的页通过列表串起来,当从磁盘加载数据时,从空闲列表中取出一个或几个页进行填充数据,并从空闲列表中删除。
脏页管理:
当 Buffer Pool 中的数据和磁盘中的数据不一样时称为脏页,通过列表管理脏页也是正确的。当数据更新时,将该页设置为脏页并添加到脏页列表中,当脏页被刷盘到磁盘时,再从列表中移出。
热点数据管理:
采用改进的 LRU 算法是正确的。将模拟链表分为 young 区域和 old 区域,从磁盘加载数据时先记录在 old 区域,当访问对应的页时,该页移动到 old 区域头部,依次迭代淘汰未访问的页。当 old 区域的某些页停留一定时间后晋升到 young 区域,保证高频访问的数据留在 Buffer Pool 中,提高命中率,防止 Buffer Pool 被不常用数据污染。
两阶段的总结
为什么要有
在更新数据时涉及到 redo log 和 binlog 的刷盘,这两个操作的执行顺序不可控。如果在更新操作后 MySQL 宕机,可能出现 redo log 刷盘成功而 binlog 未刷盘,或者反之的情况。这会导致在故障恢复后,主节点和从节点的数据不一致,因为从节点是通过 binlog 来保持数据一致性的。为了解决这个问题,引入了两阶段提交。(一句话总结就是:两阶段提交主要是为了保证redolog和binlog的数据一致性的)
两阶段提交的过程(MySQL 内部)
在 MySQL 中,特别是 InnoDB 存储引擎和 binlog 日志结合使用时,两阶段提交的过程是这样的:
流程
第一阶段:准备阶段
- 将XID(XA事务的ID)写入到redo log中,同时将redo log对应的事务状态设置为prepare准备状态,然后将redo log持久化刷盘到磁盘中(刷盘策略设置为1)。
第二阶段:提交阶段
- 将XID写到binlog中,然后将binlog持久化到磁盘文件中,接着调用存储引擎层接口,将redo log中的状态设置为commit状态,此时状态并不需要持久化到磁盘中,只需要write到文件系统的page cache中就可以了,因为只要binlog写磁盘成功,就算redo log的状态是Prepare状态,一样会被认为是事务已经执行成功
从上面不难看出决定事务最后是成功还是需要回滚还是取决于binlog中是否有对应的XID来控制,redo log在两阶段中理论上是刷盘在binlog之前,所以只要binlog中有对应的XID就代表成功
因此其他现象也就解释的通了,比如我们在修改某条数据的时候首先会在buffer pool缓冲池中进行修改,修改之后对应的页就变成了脏页,然后此时修改的数据会被同步到redo buffer中,需要注意的是redo buffer中的数据在我们没有进行commit之前也可能进行刷盘(因为操作系统会以某个速率比如每秒刷盘),但是此时由于没有进行commit那么此时binlog中是一定没有这个数据的,根据两阶段提交,就算最后数据库宕机了重启时,由于binlog中没有对应数据的XID那么就会执行回滚策略。
故障恢复过程
在MySql宕机重启之后会先按照顺去去扫描Redo log文件,当碰到prepare准备阶段的redo log时,就会拿着redo log中的XID去binlog中去查看是否存在此XID
- 如果binlog中没有当前内部XA事务的XID,那么说明redo log已经成功刷盘,但是binlog中还没进行刷盘,则回滚事务(对应时可A的宕机恢复)
- 如果binlog中有当前内部XA事务的XID,说明redo log和binlog都已经完成了刷盘,那么就提交事务(对于时刻B的宕机恢复)
两阶段所带来的问题以及解决方案
带来的问题
- 1、频繁的I/O操作(如果我们设置的刷盘策略都是每次commit都进行刷盘,那么两阶段对于每次事务都需要两次IO操作
- 2、锁竞争频繁:在mysql5.7之前为了保证事务之间的提交顺序是需要使用到锁资源的,即进入prepare阶段需要先获取锁,commit阶段完成释放锁
解决方案:
在mysql5.7之后就引入了binlog的组提交的方案:当有多个事务提交的时候,会将binlog刷盘操作合并成一个,从而减少磁盘的I/O操作次数
引入组提交机制主要是影响两阶段的commit阶段,对于prepare阶段是不变的,组操作将commit阶段分为了三个过程:每个阶段都维护一个队列来保证事务的提交顺序
- flush阶段:将多个事务按照顺序将binlog从binlog cache写入到page cache中(不刷盘),(可以理解为保存在flush阶段的队列中)
- sync阶段:对binlog文件做fsync操作(多个事务的binlog合并在一起进行刷盘)(同样将其放入sync维护的队列中)
- commit阶段:按照事务的提交顺序做InnoDB 的commit操作,即调用引擎层的提交事务接口,将redo log状态设置为commit状态
对于Sync阶段的图解流程:
执行一条更新操作的具体流程:
具体更新一条记录 UPDATE t_user SET name = 'xxx' WHERE id = 1;
的流程如下:
- 执行器负责具体执行,会调用存储引擎的接口,通过主键索引树搜索获取 id = 1 这一行记录:
-
- 如果 id=1 这一行所在的数据页本来就在 buffer pool 中,就直接返回给执行器更新;
- 如果记录不在 buffer pool,将数据页从磁盘读入到 buffer pool,返回记录给执行器。
- 执行器得到聚簇索引记录后,会看一下更新前的记录和更新后的记录是否一样:
-
- 如果一样的话就不进行后续更新流程;
- 如果不一样的话就把更新前的记录和更新后的记录都当作参数传给 InnoDB 层,让 InnoDB 真正的执行更新记录的操作;
- 开启事务, InnoDB 层更新记录前,首先要记录相应的 undo log,因为这是更新操作,需要把被更新的列的旧值记下来,也就是要生成一条 undo log,undo log 会写入 Buffer Pool 中的 Undo 页面,不过在内存修改该 Undo 页面后,需要记录对应的 redo log。
- InnoDB 层开始更新记录,会先更新内存(同时标记为脏页),然后将记录写到 redo log 里面,这个时候更新就算完成了。为了减少磁盘I/O,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入到磁盘。这就是 WAL 技术,MySQL 的写操作并不是立刻写到磁盘上,而是先写 redo 日志,然后在合适的时间再将修改的行数据写到磁盘上。
- 至此,一条记录更新完了。
- 在一条更新语句执行完成后,然后开始记录该语句对应的 binlog,此时记录的 binlog 会被保存到 binlog cache,并没有刷新到硬盘上的 binlog 文件,在事务提交时才会统一将该事务运行过程中的所有 binlog 刷新到硬盘。
- 事务提交(为了方便说明,这里不说组提交的过程,只说两阶段提交):
-
- prepare 阶段:将 redo log 对应的事务状态设置为 prepare,然后将 redo log 刷新到硬盘;
- commit 阶段:将 binlog 刷新到磁盘,接着调用引擎的提交事务接口,将 redo log 状态设置为 commit(将事务设置为 commit 状态后,刷入到磁盘 redo log 文件);
- 至此,一条更新语句执行完成。
本文主要参考MySQL 日志:undo log、redo log、binlog 有什么用? | 小林coding
目前已更新系列:
当前:MySQL----BufferPool、redolog binlog两阶段提交
Redis高级----主从、哨兵、分片、脑裂原理-CSDN博客
计算机网络--面试总结四(HTTP、RPC、WebSocket、SSE)-CSDN博客
计算机网络-------重传、TCP流量控制、拥塞控制_tcp拥塞控制,拥塞避免-CSDN博客