Innodb通过缓冲池的方式,减少查询磁盘的次数,从而提高效率。 可是内存毕竟有限,不可能将数据库表中的全部数据都存储到内存中 所以这个时候就需要内存的管理算法来维护内存空间 常见的内存管理算法有哪些呢?LRU算法,最近最少使用算法,也就是淘汰最近一段时间最少使用的页面
常用LRU算法原理
LRU一般使用链表来实现,链表有Head和Tail节点,当节点被访问的时候会将节点放到Head,如下图。线程查询page2,访问后会将page2置为头节点。
如果读入一个新的页面呢?这种时候会把TAIL节点淘汰
Inndo是否使用这种算法维护内存中的页面呢?答案是否定,传统的LRU算法不适用于Innodb的内存管理场景。
Innodb的LRU算法
在计算机中有一个通用的规则,叫做局部性原理,简单来说就是程序一般访问元素后,会访问相邻的元素。举个例子,page2有存储了1600行记录(row from 1 - 1600)。B+tree本身要求数据是按照顺序存储的,根据局部性原理,如果线程请求第100行记录,那么它一般很快就会请求100-200行之间的记录。
说完局部性原理,我们回过来再描述一下,为什么传统的LRU算法不能满足Innodb的内存管理。 Innodb采用预读的技术将磁盘数据保存在内存中,也就是数据库初始化的时候就会将一部分数据保存在内存中了。 当前预读的数据是不能确定为热点数据的,而常用的LRU算法不能够保证经常被访问的数据长时间保留在内存中,Innodb对LRU算法进行了升级。
Ps:核心原因是未升级的LRU的算法会将预读的数据放在HEAD节点
Innodb通过把链表分为两个部分new list和old list,new/old一般在7:3,Innodb通过把链表分为两个部分new list和old list,new/old一般在7:3,当预读取的页面进入缓冲池中不会直接加入到new list的head,而是加载到old list的head。这样最先淘汰的也是old list的tail,这样可以保证预读的页不会放置在链表的head,从而保留热点数据。查询命中的页会直接加入到new list的头,new list的尾部进入old list,old list的tail
Mysql缓存池污染
缓存池污染也就是使用一些全表查询,将一些实际上不常用页面加入到了缓存池中,这样会造成热点数据淘汰。
MySQL缓冲池加入了一个“老生代停留时间窗口”的机制:
- 假设T=老生代停留时间窗口;
- 插入老生代头部的页,即使立刻被访问,并不会立刻放入新生代头部;
- 只有满足“被访问”并且“在老生代停留时间”大于T,才会被放入新生代头部;
加入“老生代停留时间窗口”策略后,短时间内被大量加载的页,并不会立刻插入新生代头部,而是优先淘汰那些,短期内仅仅访问了一次的页。
Innodb内存管理比较重要的参数
参数:innodb_buffer_pool_size
介绍:配置缓冲池的大小,在内存允许的情况下,DBA往往会建议调大这个参数,越多数据和索引放到内存里,数据库的性能会越好。
参数:innodb_old_blocks_pct
介绍:老生代占整个LRU链长度的比例,默认是37,即整个LRU中新生代与老生代长度比例是63:37。 画外音:如果把这个参数设为100,就退化为普通LRU了。
参数:innodb_old_blocks_time 介绍:老生代停留时间窗口,单位是毫秒,默认是1000,即同时满足“被访问”与“在老生代停留时间超过1秒”两个条件,才会被插入到新生代头部。
1、如果在业务中做了大量的全表扫描,那么你就可以将innodb_old_blocks_pct设置减小,增到innodb_old_blocks_time的时间,不让这些无用的查询数据进入old区域,尽量不让缓存再new区域的有用的数据被立即刷掉。(这也是治标的方法,大量全表扫描就要优化sql和表索引结构了)
2、如果在业务中没有做大量的全表扫描,那么你就可以将innodb_old_blocks_pct增大,减小innodb_old_blocks_time的时间,让有用的查询缓存数据尽量缓存在innodb_buffer_pool_size中,减小磁盘io,提高性能。