应用系统的分层架构,为了加快数据访问速度,将访问频率最高的数据放在缓存(cache)中,避免每次访问数据库。操作系统会有缓冲池机制,避免每次都访问磁盘,以加快数据访问速度。MySQL作为一个存储系统,也有缓冲池机制来避免每次查询数据时的磁盘IO。今天和大家聊一聊InnoDB的缓冲池。InnoDB的缓冲池缓存什么?什么用途?缓存表数据和索引数据,将磁盘上的数据加载到缓冲池中,避免每次访问都进行磁盘IO,加快访问速度。速度快了,为什么不把所有的数据都放在缓冲池里呢?任何事物都有两个方面,抛开数据波动不谈,快速访问的反面就是存储容量小:缓存访问速度快,但容量小,数据库存储200G数据,缓存容量可能只有64G;内存访问速度快,但容量小,买2T硬盘的笔记本电脑,内存可能只有16G;因此,只能将“最热”的数据放在“最近”的地方,将磁盘访问减少到“最少”。如何管理和消除缓冲池以最大化性能?在介绍具体细节之前,先介绍一下“预读”的概念。什么是预读?磁盘读写不是按需读取,而是按页读取。一次至少读取一页数据(通常为4K)。如果以后要读取的数据在页中,可以省去后续的磁盘IO,提高效率。为什么预读有效?数据访问通常遵循“集中读写”的原则。在使用某些数据时,很有可能会使用附近的数据。这就是所谓的“局部性原则”,说明提前加载是??有效的,确实可以减少磁盘IO。按页读取(4K)与InnoDB的缓冲池设计有什么关系?按页访问磁盘可以提高性能,所以缓冲池一般也会按页缓存数据;将要访问的页面提前加入到缓冲池中,避免以后的磁盘IO操作;InnoDB使用什么算法来管理这些缓冲页?最容易想到的就是LRU(Leastrecentlyused)。画外音:Memcache和OS都是使用LRU进行页面替换管理,而MySQL的做法不同。传统的LRU是如何进行bufferpage管理的?最常见的方法是将页面作为最近访问的元素放入LRU头部的缓冲池中,使其最晚被淘汰。这里有两种情况:页面已经在缓冲池中,那么只执行“移动”到LRU头部的动作,没有页面被淘汰;页面不在缓冲池中,除了“放入”到LRU动作的头部,还有“淘汰”LRU尾页的动作;如上图所示,如果管理缓冲池的LRU长度为10,则缓冲页码为1,3,5...,40,7的页。假设,接下来要访问的数据在页号为4的页中:页号为4的页已经在缓冲池中;只是把页码为4的页放在LRU的头部,没有页被淘汰;画外音:为了减少数据移动,LRU一般用链表来实现。假设,接下来要访问的数据在页号为50的页中:页号为50的页不在缓冲池中;将页码为50的页放在LRU的头部,同时剔除尾部页码7页;传统的LRU缓冲池算法非常直观,OS、memcache等很多软件都会用到。为什么MySQL这么虚伪,不能直接用?这里有两个问题:预读失败;缓冲池污染;什么是预读失败?由于预读(Read-Ahead),页面被提前放入了缓冲池,但是最终MySQL并没有从该页面中读取数据,这种情况称为预读失败。如何优化预读失败?优化预读失败的思路是:让预读失败的页面在缓冲池LRU中停留的时间尽可能短;让真正被读取的页面移动到缓冲池LRU,以保证真正被读取的热数据尽可能长时间地留在缓冲池中。具体做法是:(1)将LRU分成两部分:新生代(newsublist)和老年代(oldsublist)(2)新生代和老年代在末尾相连,即新一代的尾部连接到老一代(头部);(3)当一个新页(如预读页)加入到缓冲池中时,只加入到老年代的头部:如果数据真正被读取(预读成功),则将加入到新生代中,如果header中的数据还没有被读取,则在新生代中比“热数据页”更早的从缓冲池中淘汰。例如整个缓冲池LRU如上图所示:整个LRU长度为10;新一代;最后30%是老年代;老年代首尾相连;如果预读一个页号为50的新页加入缓冲池:50只会从老年代的头部插入,老年代的尾部(也就是整个尾部的页)会被淘汰;假设第50页不会被实际读取,即预读失败,则它会比新生代的数据更早从缓冲池中淘汰;如果立即读取第50页,例如SQL访问该页中的行数据:会立即添加到新生代的头部;新生代的页面会被挤到老年代,此时不会真正淘汰掉任何页面;改进的缓冲池LRU可以解决“无法预读”的问题。画外音:但不要因为害怕噎住而放弃进食,也不要因为害怕预读失败而取消预读策略。在大多数情况下,局部性原则是成立的,预读是有效的。针对新老代的改进版LRU,依然无法解决缓冲池污染的问题。什么是MySQL缓冲池污染?当某条SQL语句批量扫描大量数据时,可能会导致缓冲池中的所有页面被替换,导致大量热数据被换出,MySQL性能急剧下降。这种情况称为缓冲池污染。比如有一个user表,数据量很大,执行时:select*fromuserwherenamelike"%shenjian%";虽然结果集可能只有少量数据,但是这种like无法索引,必须全量扫描,需要访问大量页:将页加入缓冲池(插入页首)老一辈);从页面中读取相关行(插入新生代的头部);将行中的name字段与字符串shenjian进行比较,如果符合条件,则将其添加到结果集中;...直到扫描完所有页中的所有行...这样,所有的数据页都会被加载到新生代的头部,但只访问一次,真正的热点数据会被大量换出。为什么扫描大量数据会造成这种缓冲池污染呢?MySQL缓冲池增加了“老年代驻留时间窗口”机制:假设T=老年代驻留时间窗口;在老年代的头部插入页面,即使立即被访问到,也不会立即放入新生代的头部;只有“visited”和“在oldgeneration的停留时间”大于T才会放入newgeneration的头部;将依次访问51、52、53、54、55等五个页面。如果没有“老年代停留时间窗口”策略,这些被批量访问的页面会换出大量的热点数据。加入“老年代停留时间窗口”策略后,短时间内大量加载的页面不会立即插入到新生代的头部,而是那些只被访问过一次的页面在短时间内会先被淘汰。只有当你在老年代停留的时间足够长,并且停留时间大于T时,才会被插入到新生代的头部。InnoDB中有哪些参数符合上述原则?有三个重要的参数。(1)参数:innodb_buffer_pool_size简介:配置缓冲池的大小。如果内存允许,DBA通常会建议增加此参数。内存中放置的数据和索引越多,数据库的性能就越好。(2)参数:innodb_old_blocks_pct简介:老年代占整个LRU链长度的比例默认为37,即整个LRU中新生代和老年代的长度比例为63:37.画外音:如果把这个参数设置为100,就会退化成普通的LRU。(3)参数:innodb_old_blocks_time简介:老年代停留时间窗口,单位毫秒,默认1000,即只有满足“被访问”和“停留在老年代”两个条件才会插入新生代超过1秒”代表头部同时满足。总结(1)缓冲池(bufferpool)是一种常用的减少磁盘访问的机制;(2)缓冲池通常以页(page)为单位缓存数据;(3)缓冲池常用的管理算法有LRU、memcache、OS、InnoDB都采用该算法;(4)InnoDB优化普通LRU:将缓冲池分为老年代和新生代。新生代,解决预读失效问题,当页面被访问时才进入新生代,停留在老年代的时间超过配置的阈值,解决批量数据访问和大批量淘汰的问题热点数据量【本文为专栏作者《58神剑》原稿,转载请联系原作者】点此查看该作者更多好文
