Buffer Pool是MYSQL数据库中的一个重要的内存组件 ,介于外部系统 和存储引擎 之间的一个缓存区 ,针数据库的增删改查这些操作都是针对这个内存数据结构中的缓存数据执行的,在操作数据之前,都会将数据从磁盘加载到Buffer Pool中,操作完成之后异步刷盘 、写undo log、binlog、redolog等一些列操作,避免每次访问都进行磁盘IO影响性能。
Buffer Pool基本概念
Buffer Pool:缓冲池,简称BP。其作用是用来缓存表数据 与索引数据 ,减少磁盘IO操作,提升效率。
Buffer Pool由 缓存数据页(Page) 和 对缓存数据页进行描述 的控制块 组成, 控制块中存储着对应缓存页的所属的 表空间 、数据页的编号、以及对应缓存页在Buffer Pool中的地址等信息.
Buffer Pool默认大小是128M , 以Page页为单位 ,Page页默认大小16K ,而控制块的大小约为数据页的5%,大概是800字节。
注: Buffer Pool大小为128M指的就是缓存页的大小 ,控制块则一般占5%,所以每次会多申请6M的内存空间用于存放控制块
MySQl中有一个哈希表数据结构 ,它使用表空间号+数据页号 ,作为一个key ,然后缓冲页 对应的控制块作为value。
-
当需要访问某个页的数据时,先从哈希表中根据表空间号+页号看看是否存在对应的缓冲页。
-
如果有,则直接使用;如果没有,就从free链表中选出一个空闲的缓冲页,然后把磁盘中对应的页加载到该缓冲页的位置
InnoDB如何管理Page页?
Page页分类
BP的底层采用链表数据结构 管理Page。在InnoDB访问表记录和索引时会在Page页中缓存,以后使用可以减少磁盘IO操作,提升效率。
Page根据状态可以分为三种类型
* free page : 空闲page,未被使用
* clean page:被使用page,数据没有被修改过
* dirty page:脏页,被使用page,数据被修改过,Page页中数据和磁盘的数据产生了不一致
Page页如何管理
针对上面所说的三种page类型,InnoDB通过三种链表结构来维护和管理
1.free list :表示空闲缓冲区,管理free page
free链表是把所有空闲的 缓冲页对应的控制块作为一个个的节点放到一个链表中,这个链表便称之为free链表
基节点: free链表中只有一个基节点 是不记录缓存页信息 (单独申请空间),它里面就存放了free链表的头节点 的地址,尾节点 的地址,还有free链表里当前有多少个节点。
2.flush list : 表示需要刷新到磁盘 的缓冲区,管理dirty page,内部page按修改时间排序。
-
InnoDB引擎为了提高处理效率,在每次修改缓冲页后,并不是立刻把修改刷新到磁盘上,而是在未来的某个时间点进行刷新操作. 所以需要使用到flush链表存储脏页,凡是被修改过的缓冲页对应的控制块都会作为节点加入到flush链表.
-
flush链表的结构与free链表的结构相似
3.lru list :表示正在使用 的缓冲区,管理clean page和dirty page ,缓冲区以midpoint 为基点,前面链表称为new列表区 ,存放经常访问的数据,占63%;后面的链表称为old列表区,存放使用较少数据,占37%
为什么写缓冲区,仅适用于非唯一普通索引页?
change Buffer基本概念
Change Buffer:写缓冲区,是针对二级索引(辅助索引) 页的更新优化措施。
作用: 在进行DML操作时 ,如果请求的辅助索引(二级索引)没有在缓冲池中时,并不会立刻将磁盘页加载到缓冲池,而是在CB记录缓冲变更,等未来数据被读取时,再将数据合并恢复到BP中。
-
ChangeBuffer用于存储SQL变更操作,比如Insert/Update/Delete等SQL语句
-
ChangeBuffer中的每个变更操作都有其对应的数据页 ,并且该数据页未加载到缓存中;
-
当ChangeBuffer中变更操作对应的数据页加载到缓存中后,InnoDB会把变更操作Merge到数据页上;
-
InnoDB会定期加载ChangeBuffer中操作对应的数据页到缓存中,并Merge变更操作;
change buffer更新流程
写缓冲区,仅适用于非唯一普通索引页,为什么?
- 如果在索引设置唯一性,在进行修改时,InnoDB必须要做唯一性校验 ,因此必须查询磁盘 ,做一次IO操 作。会直接将记录查询到BufferPool中,然后在缓冲池修改,不会在ChangeBuffer操作。
MySQL为什么改进LRU算法?
普通LRU算法
面试题:设计一个最近最少使用的数据结构。
LRU = Least Recently Used(最近最少使用 ): 就是末尾淘汰法 ,新数据从链表头部加入 ,释放空间时从末尾淘汰.
-
当要访问某个页时,如果不在Buffer Pool,需要把该页加载到缓冲池,并且把该缓冲页对应的控制块作为节点添加到LRU链表的头部。
-
当要访问某个页 时,如果在Buffer Pool中,则直接把该页对应的控制块移动到LRU链表的头部
-
当需要释放空间时,从最末尾淘汰
普通LRU链表的优缺点
优点
- 所有最近使用的数据都在链表表头,最近未使用的数据都在链表表尾,保证热数据能最快被获取到。
缺点
-
如果发生全表扫描 (比如:没有建立合适的索引 or 查询时使用select * 等),则有很大可能将真正的热数据淘汰掉.
-
由于MySQL中存在预读机制,很多预读的页都会被放到LRU链表的表头。如果这些预读的页都没有用到的话,这样,会导致很多尾部的缓冲页很快就会被淘汰。
改进型LRU算法
改进型LRU:将链表分为new和old两个部分 ,加入元素时并不是从表头插入,而是从中间midpoint位置插入 (就是说从磁盘中新读出的数据会放在冷数据区的头部),如果数据很快被访问,那么page就会向new列表头部移动,如果数据没有被访问,会逐步向old尾部移动,等待淘汰。
冷数据区的数据页什么时候会被转到到热数据区呢 ?
-
如果该数据页在LRU链表中存在时间超过1s,就将其移动到链表头部 ( 链表指的是整个LRU链表)
-
如果该数据页在LRU链表中存在的时间短于1s ,其位置不变(由于全表扫描有一个特点,就是它对某个页的频繁访问总耗时会很短)
-
1s这个时间是由参数 **
innodb_old_blocks_time
**控制的
Buffer Pool优化建议
1. 调整Buffer Pool内存大小
参数:innodb_buffer_pool_size,内存缓冲区大小,在内存允许的情况下,建议调大该参数值,越多的数据和索引放入缓冲区,查询性能越好。
如何设置生产环境数据库的Buffer Pool的合理内存大小,保证数据库的高性能和高并发能力?
建议调整为机器内存大小的50%~60%。
为什么不设大点?因为需要考虑操作系统占用的内存以及其他应用占用的内存、以及MYSQL中其他数据结构占用的内存。
2. 划分多个Buffer Pool区域提升数据库并发性能
在多线程并发访问同一个Buffer Pool时,他们都是访问内存中共享的数据结构,如缓存页、free链表、flush链表的等,此时必然要加锁,导致很多线程必然要串行排队,所以说是在内存中进行排队,延迟很低,但是也是有进一步优化提升性能的空间。
那如何优化呢?
给MYSQL设置多个Buffer Pool。
MYSQL默认规则是,如果Buffer Pool内存空间小于1GB,最多只分配一个Buffer Pool,如果内存空间较大,则可以设置多个Buffer Pool,配置如下:
[server]
innodb_buffer_pool_size=8589934592
innodb_buffer_pool_instances=4
上面的配置给Buffer Pool分配了8GB内存 ,设置了4个Buffer Pool,每个Buffer Pool大小就是2GB。
这样,多线程并发访问的时候就可以将压力分摊开来 ,多个线程可以在不同的Buffer Pool中进行加锁和执行操作,从而成倍提升多线程并发访问能力。(有点类似于ConcurrrentHashMap的分段加锁机制)
知识来源:马士兵教育