Hi,大家好,我是抢老婆酸奶的小肥仔。
Mysql是我们日常中用的比较多的关系型数据库。今天我们就来聊一聊什么是MVCC与Buffer Pool缓存机制。
开聊!
1、MVCC
1.1 概念
MVCC:(Multi-Version Concurrency Control)多版本并发控制,主要解决事务的隔离性,对一行数据的读和写两个操作默认不通过加锁互斥来保证隔离性,避免频繁加锁互斥。
MVCC在读已提交和可重复读隔离级别都已实现
1.2 undo日志版本链与read view机制
1.2.1 undo日志版本链
undo日志:即记录Mysql执行的数据的历史,便于进行回滚。
undo日志版本链:指一行数据被多个事务进行修改后,在每个事务修改完成后,MySQL会保留修改前的数据undo回滚日志,并用两个隐藏字段trx_id和roll_pointer把这些undo日志串联起来形成一个历史记录版本链。如图:
注:trx_id:事务Id roll_pointer:指向上一条记录的指针。
1.2.2 read view
read view:一致性视图,在可重复读隔离级别,当事务开启后,第一次执行查询SQL时生成。在事务提交前,该视图不发生任何变化。(读已提交隔离级别,每执行查询SQL都生成新的read view)。
组成:未提交事务Id组成的数组(最小事务idmin_id)和已创建的最大事务Id(max_id)
例如:假设有三个事务,其事务id分别是100,200,300,都未提交,此时的readview为:read view:[100,200],300
版本链对比规则:
即将read view分作三部分:
1、如果row的trx_id在绿色部分(即trx_id < min_id),则表示该版本是已提交的事务,这个数据是可见的
2、如果row的trx_id在黄色部分(即trx_id > max_id),则表示该版本是由将来启动的事务生成的,是不可见的(若row的trx_id就是当前自己的事务,则可见)
3、如果row的trx_id在红色部分(即min_id < trx_id < max_id),则包含两种情况:
a、若row的trx_id在视图数组中,表示这个版本是由还没提交的事务生成,不可见(若row的trx_id就是当前自己的事务,则可见)
b、若row的trx_id不在视图数组中,表示这个版本是已经提交了的事务生成,则可见
注:删除的情况可以认为是update的特殊情况,会将版本链上最新的数据复制一份,然后将trx_id修改成删除操作的trx_id,同时在该条记录的头信息(record header)里的(deleted_flag)标记位写上true,来表示当前记录已经被删除,在查询时按照上面的规则查到对应的记录如果delete_flag标记位为true,意味着记录已被删除,则不返回数据。
2、Buffer Pool缓存机制
buffer pool:即以页的单位进行存储的mysql内存区域。默认大小:128M。是InnoDB存储引擎特有的。
Mysql的增删改查都是在Buffer pool上执行,之后会随机将buffer pool中的修改日志同步到磁盘中。
流程图:
步骤:1、将磁盘中的数据以所在页的形式,加载到buffer pool中
2、写入undo日志,保存当前数据的旧数据,便于进行事务回滚
3、更新buffer pool中的数据
4、写入redo日志,
5、准备提交事务,将redo日志写入磁盘,保证当事务提交后,数据没有写入磁盘时,能够恢复buffer pool数据
6、准备提交事务,将binlog日志写入磁盘
7、commit事务,标记到redo日志,保证redo日志与binlog日志一至
8、随机将buffer pool中的数据更新到磁盘中
2.1 怎样识别数据在哪个缓存页?
每个缓存页都对应一个描述数据块,该数据块包含数据页所属的表空间、数据页的编号,缓存页在Buffer Pool中的地址等。本身为一块数据,大小是缓存页大小的5%左右。因此,描述数据块在前,缓存页在后。如图:
2.2 Buffer Pool初始化与配置
1、Mysql启动时,根据配置参数innodb_buffer_pool_size的值来分配Buffer Pool内存大小
2、按缓存页默认大小16kb以及对应的描述数据块800字节左右大小,在Buffer Pool中划分一个个缓存页和描述数据块。
注:此时缓存页、描述数据块都为空
配置:
1、innodb_buffer_pool_size: 设置InnoDB Buffer Pool的总大小
2、innodb_buffer_pool_chunk_size: 当增加或减少innodb_buffer_pool_size时,操作以块(chunk)形式执行。块大小由innodb_buffer_pool_chunk_size配置选项定义。
配置规则:innodb_buffer_pool_size=innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances*N(N>=1);
3、innodb_buffer_pool_instances: 设置InnoDB Buffer Pool实例个数,每个实例都有自己独立的list管理。innodb_buffer_pool_size必须是innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances的整数倍,否则Buffer Pool会自动增大,调整为整数倍。
例如:size大小为9G,instances为16,chunk_size为128M,则Buffer Pool会自动调整为10G。
4、innodb_old_blocks_pct: 默认InnoDB Buffer Pool中点的位置,默认大小是37,最大100,即3/8的位置。
设置Buffer Pool大小及相关操作:
set global innodb_buffer_size=4050310212; //设置size大小
SHOW STATUS WHERE Variable_name='InnoDB_buffer_pool_resize_status' //查看Buffer Pool状态
SELECT @@innodb_buffer_pool_size/1024/1024; //查看buffer pool大小,单位为G
show global status like 'Innodb_buffer_pool_pages_total'; //查询buffer pool总页数
show global status like 'Innodb_buffer_pool_pages_data'; //查询buffer pool包含数据的页数
语句执行后,需等待所有事务全部执行后才生效,而新连接和事务需等到设置生效后才能连接,否则一直处于等待状态
2.3 冷热数据分离
相对于传统LRU方法,Mysql采用了冷热数据分离的处理方案。
主要解决问题:
(1)预读失效:即相邻的两个页,只有其中一个页号被访问时,则Mysql会将相邻的另一个数据页也加载进Buffer Pool,此时就会淘汰LRU链表的最后一个缓存页,如果被淘汰的缓存页被经常访问,而预读的数据没有访问,因此会存在问题。
(2)缓存池污染:某个查询时扫描了大量数据或进行了全表扫描,会将缓存池中的数据全部替换。
实现逻辑:
因此Mysql采用了冷热数据分离,将LRU链表分成冷热两部分。如图:
冷数据区数据被加载到缓存后,1s (innodb_old_blocks_time参数值决定,单位ms) 内后再次被访问,在被移至热数据区域
2.4 Buffer Pool三种Page和链表
2.4.1 三种page
分别是:Free Page(空闲页)、Clean Page(干净页)、Dirty Page(脏页)
Free Page(空闲页):即缓存页中没有任何数据
Dirty Page(脏页):被修改的数据,即与磁盘中的数据不一致
Clean Page(干净页):没有被修改过的数据,且与磁盘中数据一致,只是做了查询操作
2.4.2 三种链表
LRU链表,Flush链表,Free链表
LRU链表: 最近最少使用链表,即将分为冷热数据区。
(1)冷数据区占整个LRU链表比例的3/8,由innodb_old_blocks_pct控制,默认值是37(3/8 * 100),值范围:5~95
(2)新页被读取到Buffer Pool时,会插入热数据区域后,冷数据区后的中间位置,即Mid Point
(3)频繁被访问的数据,会往热数据区域头部移动,预读的数据如果短时间内没有被访问,则很可能在下次访问前被移到冷数据区域尾部而被移除
(4)新数据的不断添加,会不断插入Mid Point,冷数据区域会被不断移至尾部,常被访问的数据移至热数据区域头部,那些没被访问的页会逐渐移至热数据区域而被移除。
注:只有一个被访问数据页处于热数据区域长度的1/4(大约值)之后,才会被移动到热数据区域链表的头部。这样做的目的是减少对LRU 链表的修改,因为LRU 链表的目标是保证经常被访问的数据页不会被驱逐出去。
Flush链表:
(1)保存的都是脏页,且保存的页存在于LRU链表
(2)按照oldest_modification排序,值大的在头部,值小的在尾部
(3)当页面被修改时,使用mini-transaction,对应的page进入Flush链表
(4)page第一次修改时进入Flush,其他次数修改不加入
(5)Page Cleaner线程执行flush操作时,从Flush链表开始scan,将脏页写入磁盘,突进检查点,减少recover时间。Flush链表中脏页刷回磁盘中,MySQL提供了一个后台线程执行。
Free链表:
(1)空闲页面,初始化的时候申请一定数量的页面,Mysql使用过程中,空闲页不断不断减少
(2)执行SQL时,数据加载到内存后,会判断Free链表页够不够,不够时执行flush LRU链表和Flush链表来释放空间。够用时,删除对应页面,LRU链表中添加。