**mysql的INNODB引擎是基于磁盘存储的,管理方式也是按照页管理的,表空间结构大概如下:

InnoDB缓存 数据量大时对索引影响 mysql缓存池_mysql


如果所有的请求都需要磁盘IO的话,那无疑是很耗时的,所以在中间就有一层缓存来进行缓冲,mysql的缓存池就是这个缓冲的角色,通过缓存池可以弥补部分cpu和磁盘的速度差距。

读操作

如果数据是首次读,那么按页读取后会将磁盘读出来页的数据存到缓存池,这样如果下次获取相同的页时,就不会区磁盘读取,而直接返回缓存池中的数据页。

写操作

要写数据肯定需要先获取数据,那么首先会读,然后,对要写的数据并不会直接被写入到磁盘,而实先修改缓存池中的页,然后根据一定的频率来将这个脏页(dirty page)刷到磁盘,这里又涉及到**checkpoint**机制。

缓存池中主要包含了以下部分
  • 数据页
  • 索引页
  • 插入缓存
  • 自适应哈希索引
  • 锁信息
  • 数据字典信息
缓存池中的每个缓存页又包含以下控制信息,称为控制块
  • space id _表空间编号
  • page numeber _页号
  • 页地址 _在bufferPool中的地址
  • 锁信息以及 LSN 信息日志序列号
  • 其他信息
缓存池的管理

缓存池中大部分是数据页和索引页,为了管理这些页,就有free链表,lru链表和flush链表。

InnoDB缓存 数据量大时对索引影响 mysql缓存池_链表_02


mysql服务启动时,buffer pool初始化,buffer pool被划分为控制块和缓存块,此时这些缓存页是空的,随着服务的使用,不断会有磁盘数据页被刷到缓存中,为了记录哪些是缓存页使用了的,哪些缓存页是空闲的,在一开始,就将这些缓存页的控制单元记录到free链表中,这样就可以通过free链表来管理缓存页,当需要使用缓存页时,从free链表申请空闲的缓存页,然后让lru链表的节点记录该页的控制单元信息,这样,就将使用的和空闲的分开来,同时,合理的淘汰掉不常用的缓存页,将空页还给free链表;对于写请求造成的脏页,会在一个flush链表中记录它的信息,不同的是,flush链表记录的是它在lru链表的信息,这样就可以最终找到脏页,再把脏页刷到磁盘。

InnoDB缓存 数据量大时对索引影响 mysql缓存池_链表_03

  • 对于lru链表中节点的快速定位,是使用了一个哈希表来处理的,
    哈希表的key是使用缓存页的space id _表空间编号 + page numeber _页号组成的,而value则是缓存页,这样就不需要遍历链表,O(1)时间就能找到缓存页。
  • 对于lru的淘汰策略,则是链表被分为了新生代和老生代,新生代和老生代的比例为5:3,对于链表中已有的数据页。
  • 节点被访问时,如果是新生代的节点,则会被移动到链表的head节点;
  • 如果是老生代的节点,那么当这个节点被访问时,已经在链表中存在了1s以上,则会把它移动到head节点,否则,保持当前位置。
  • 如果是新加入一个数据页,则是将它放到老生代的o1节点,如果当前链表容量达到了innodb_buffer_pool_size(大约占物理内存的60%-80%),还需要移除tail节点;

InnoDB缓存 数据量大时对索引影响 mysql缓存池_java_04


mysql在做全表扫描时,在server层是这么执行的
对执行层返回的结果整合成结果集,发送到mysql的net-buffer,
net-buffer满了以后如果引擎层还没查询完成,net-server就会调用网络接口发送数据到客户端,然后清空net-buffer,来接收后续的结果。
如果发送函数返回 EAGAIN 或 WSAEWOULDBLOCK,就表示本地网络栈(socket send buffer)写满了,进入等待。直到网络栈重新可写,再继续发送。
在INNODB执行引擎层内存页是受buffer-pool管理的,对于全表扫描这种场景,数据中非热点数据居多,基本会被放到old,不会影响young的内存数据,也就不会使热点缓存页失效。缓存页置换导致大量磁盘io,从而影响mysql的性能