我们知道,MySql语句执行流程:
客户端->连接器->查询缓存->分析器->优化器->执行器->InnoDB存储引擎
先上一张InnoDB架构图:
左边是内存区域,右边是磁盘区域;
本篇主要介绍图中的 Buffer Pool:
顾名思义Buffer Pool就是内存缓存区,比如现在执行一条sql:select * from t1 where a = 1;
InnoDB会去磁盘里找到这条数据对应的页,如下图:
然后把这一
页,复制到Buffer Pool里面,如下图:
此时,如果再去执行
select * from t1 where a = 1;,InnoDB会直接去Buffer Pool里面获取数据,不会再到磁盘里面获取;
同理,执行update t1 set b = 2 where a = 1;,InnoDB会去磁盘里找到这条数据对应的页,然后把这一页,复制到Buffer Pool里面,然后直接改Buffer Pool里面的数据;此时,内存和磁盘数据不一致,InnoDB内部会定期把Buffer Pool数据持久化到磁盘;
Buffer Pool的内存大小默认为:128M,如果当前缓存了许多页数据,如下图:
注:Buffer Pool为数组结构,是一长串,此处为了方便显示,展示成多排样式!
上面说到,InnoDB会定期把Buffer Pool数据持久化到磁盘,其实就是拿Buffer Pool更新后的数据覆盖磁盘中的老数据。这就会有一种情况,如下图:
白底
页是已经从Buffer Pool中移除并且持久化到磁盘的数据,黄底页是还在Buffer Pool中未持久化到磁盘中的数据;
说白了,此时Buffer Pool中除了黄底页,其他都是空出来的位置。如果这时候,有新的数据要加入Buffer Pool中,该放在什么位置呢?(因为空闲的位置是散乱的,不是连续的)
InnoDB有一个链表,叫做:free链表,专门用来记录空闲位置,如下图:
基节点:记录此链表的信息:头节点、尾节点、链表长度 ...
控制块:记录指向空闲位置的指针;
Buffer Pool里面有多少个空闲位置,free链表就有多少个控制块;(此处为了方便只画了3个)
此时如果往Buffer Pool放入数据,直接去free链表找到控制块对应的空闲位置,把数据放入空闲位置,然后把这个控制块从free链表里移除掉,同理,如果Buffer Pool里某一页空出来了,会生成一个控制块并指向当前空出来的这个位置,添加到free链表;
什么时候InnoDB会把Buffer Pool里修改过的数据持久化到磁盘中呢?
上图中:有一页被修改过,InnoDB中有一个链表,叫做:flush链表,专门用来记录修改过的页(脏页),结构和free链表是一样的;
后台有一个线程,会定期遍历flush链表,持久化到磁盘;
由于Buffer Pool默认大小为128M,如果此时Buffer Pool满了,再有新数据想放进去,这么办呢?
InnoDB内部还有一个链表,叫做:lru链表;结构和free链表是一样的
每次新加入Buffer Pool的数据页,都会往lru链表的头部插入一个控制块并指向这个数据页,因为是最近使用到的;同理,任何操作Buffer Pool里面的数据页,都会把这页对应的控制块移动到lru链表的头部;
lru链表中,越是前面的位置(热点数据),越是近期被访问到的,越是后面的位置,越是最早之前被访问到的;
如果Buffer Pool满了,此时有新数据进来,移除lru链表末尾的控制块及指向的数据,往链表头部插入新控制块;
有个问题:如果执行select * from t1 ,岂不是会把Buffer Pool中缓存的数据全部给替换了,这个叫做换血;
为了解决上面这个问题,InnoDB把lru链表分成了两个区域:
黄色区域表示:热数据区域;
绿色区域表示:冷数据区域;
如果有新数据进Buffer Pool,先移除冷数据区域末尾的控制块及指向Buffer Pool的数据,新数据对应的控制块会放到冷数据区域头节点的位置:
那什么时候会把冷数据区域的数据页,移动到热数据区域呢?
冷数据区域的数据,当前被访问的时间减去上一次被访问的时间大于1秒,此时会把访问到的数据块从冷数据区域移动到热数据区域的头节点;
到这里,还没讲解Buffer Pool里的数据到底是如何持久化到磁盘中的?又是如何防止数据丢失的?
先介绍两个log:
redo log:Buffer Pool中某一页哪个位置的数据进行了修改;
undo log:redo log 的反向操作,记录如何回到原始数据的sql;
update语句执行的时候:
1.修改Buffer Pool里的数据;
2.生成一个 redo log;
3.redo log 持久化;
4.undo log;
5.修改成功;
那为什么不直接持久化到数据库磁盘,而是要生成并持久化一个 redo log 呢?
我们知道,一页数据=16KB,update只是修改了这一页中的一行数据,如果直接持久化到数据库磁盘,就需要把整页的数据全部持久化,这整页的数据在磁盘里的分布是随机的,这个成本就很大;
redo log 对应的文件:
var/lib/mysql/ib_logfile0
var/lib/mysql/ib_logfile1
这两个文件是mysql一开始自动生成的,往里面顺序追加数据会很快;哪怕中间mysql挂掉了,下次mysql重启,还可以基于 redo log 恢复数据;
如果这两个log文件全部满了,再有新数据进来,该怎么办呢?
针对上面这种情况,会触发InnoDB的检查点,InnoDB会把 redo log 数据对应Buffer Pool中的页全部持久化到数据库磁盘,然后清空 redo log,接着才能在 redo log 中插入新数据;当然,这个处理过程,mysql性能是很慢的。
之前说到update语句执行时:
1.修改Buffer Pool里的数据;
2.生成一个 redo log;
3.redo log 持久化;
4.undo log;
5.修改成功;
上面的第2步:生成的 redo log 会先放在Log Buffer内;(参照文章开始的InnoDB架构图)
上面的第3步:redo log 持久化是在事务提交的时候;
这就是InnoDB事务的执行流程:
begin开始事务后,如果commit,就持久化 redo log,如果rollback,就执行 undo log 记录的数据还原操作;
我们知道事务的四大特性:ACID,即:原子性、一致性、隔离性、持久性
原子性:undo log 来实现;
持久性:redo log 来实现;
隔离性:MVCC 来实现;
一致性:上面三种最终保证一致性;