mysql-buffer pool粗略理解

813 阅读5分钟

你在拼命往前,我却拖住后腿。

纯个人理解与回忆

why buffer pool?

当数据都存储在磁盘的时候,读取与写入都是随机操作磁盘,特别慢。试想,当并发量很高的时候数据库性能无法保证。因此,为了提高性能,mysql设计了一套基于buffer pool和本地文件存储的系统。

what is buffer pool?

buffer pool实际上是一块内存区域,可以看作是磁盘中数据的缓存,由于在内存中对数据进行操作,性能非常高。

mysql在启动的时候会申请一块内存区域,提前划分出空的数据页(通常16KB),在用户角度来看是表/字段/行,这些是逻辑概念,物理上是表空间,数据页等概念,并且每页会有数据描述模块给出该页的基本信息,每页还会与三个双向链表进行绑定,在读取,写入,刷脏页等场景下动态变化。free空闲链表,存储可供磁盘中数据页载入的空闲页;flush链表,存储与磁盘中数据不一致的脏也;lru链表,为了将最少访问的数据页刷入磁盘,这三个链表都分别有一个表头类似哨兵机制,链表元素是各页的描述信息。不仅如此,还有一个hash数据结构用来表示某个数据页是否在buffer pool中。具体可以看下图所示。

数据页随着读写操作在buffer pool中的动态过程

  • 读取 如果该行数据所在的数据页已经存在在buffer pool中,则直接获取数据返回。如果不存在,会在free链表中获取一个空数据页,将从磁盘中读取到的该行数据所在的数据页加载到空数据页中,然后返回数据。

  • 写入 mysql首先会将操作记录在undo log中用于数据多版本管理,然后顺序写入redo log中用于crash-safe,且可以提高写入性能,如果该行数据所在的数据页已经存在在buffer pool中,则直接更新该数据页,并将该数据页的描述信息加入到flush链表中,表明该页为脏页。如果不存在,则写入redo log认为更新完成。如果紧接着要读取,内存中还是没有,会从磁盘加载,并和redo log的记录合并后返回,为了提高在这种场景下的读取效率,mysql还提供了change buffer的机制,会将操作同时记录到change buffer中,更新的时候认为写入到change buffer即可,也就是到需要的时候才加载数据。

  • lru buffer pool的大小是有限的,甚至可以说是固定的,如果buffer pool满了该如何呢,lru链表提供了其中一个解决方案。mysql中的lru又提供了一个适用于自己的改良版本lru。

为什么原先的lru不适合呢?场景一:预读,mysql的预读机制下,会将需要的数据页周围的数据页也都读入buffer pool中,为了提高查询效率;场景二:全表查询,select * from xxx没有where或者没有建索引的时候,会将全表数据读入。在这两种场景下,会出现经常访问的已经在buffer pool中的热点数据往链表尾部也就是较少用到的区域流动,而新插入的数据可能只是一次查询而已,并非真正的热点数据,这种操作会使得真正热点数据无法停留在buffer pool中,被大量的一次性数据赶走,从而缓存命中率降低,查询效率大大降低。

mysql提出的改良版本是将lru分成冷热两个区域,有一个比例来区分,数据页首先加入冷区头,这样热区的数据就不会被赶走了,并且会设置一个参数,比如1秒,当冷区的数据在1秒后再次被访问到,就被放到热区的头部。

  • 脏页刷入磁盘 为了保证buffer pool始终有空闲空间,首先会有一个后台线程定期将脏页刷入磁盘,通常是还没有等所有空间都被用完而是超过了一个阈值,就会将flush链表中的脏页刷入磁盘。同时还有一个后台线程在系统没有那么忙的时候将redo log中的数据刷入磁盘。通过这样的操作,可以保证系统的一个平衡,一边各种查询更新线程进行操作消耗空闲数据页,一边后台线程定期将脏页刷入磁盘释放出空闲数据页。

但是如果后台线程释放的速度赶不上线程消耗的速度呢,当free链表中没有空闲数据页的时候,而此时来了一个查询请求呢?就要用到lru链表了,将lru尾部不常用的脏页刷入磁盘,那么此时就会发生一次查询要进行两次磁盘的随机读写,先是将一批脏页刷入,然后将需要的数据页读出,对于mysql查询效率是大大降低的,该如何是好呢?方法就是增大buffer pool的大小,因为后台线程何时释放数据页是无法控制的,而线程消耗空闲数据页的速度也是无法控制的,只能增大buffer pool,尽可能的配置大一点的空间,一搬是一台机器50%~60%的大小。

buffer pool的数量

为了扛住并发,多个实例一起,负载均衡;为了保证数据可靠性,一主多从。

当线程来的时候如果只有一个buffer pool, 线程们肯定要获取锁,成功获取的可以对buffer pool进行操作,没有获取到的进行等待。是否可以有多个buffer pool呢?当然是的,每个buffer pool管理一部分的数据,个人猜测类似一致性哈希算法将数据路由到buffer pool上。

buffer pool的大小

为了能够让buffer pool可以扩容,mysql通过chunk机制,将一组数据页认为是一个chunk(通常128M),所有chunk共享三个链表。