简介
InnoDB存储引擎是基于磁盘存储的,由于CPU速度与磁盘速度之间天差地别,因此引入缓冲技术来提高整体性能。
缓冲池,是主存中InnoDB缓存被访问的表和索引数据的区域。缓冲池允许直接从内存中处理经常使用的数据,从而加快处理速度。
例如,客户端进行读取、修改操作:
- 读取操作:首先从磁盘上读取到的页放在缓冲池中,下一次读取相同的页时,先判断该页是否在缓冲池中,若在,则称作页在缓冲池被命中,否则需要去磁盘上读取;
- 修改操作:首先修改在缓冲池中的页(修改过的页,称为脏页),然后通过Checkpoint机制,按照一定的频率刷新到磁盘上。
在专用服务器上,高达80%的物理内存通常分配给缓冲池。
可以通过innodb_buffer_pool_size
配置缓冲池的大小(默认8M),还可以通过innodb_buffer_pool_instances
配置缓冲池的数量(默认1个)。
主要注意的是,缓冲池大小
innodb_buffer_pool_size
必须始终等于innodb_buffer_pool_chunk_size * innodb_buffer_pool_instances * K
(K是个大于0的常数),因此如果设置了个不满足上述条件的innodb_buffer_pool_size
,innoDB会帮你自动调整为最接近上述条件的一个值。
缓冲池缓存数据对象
为了提高大容量读操作的效率,缓冲池被划分为可以容纳多行的页。
如图所示,innoDB缓冲池缓存的数据对象主要有:
- 数据页;
- 索引页;
- undo页;
- 变更缓冲;
- 自适应哈希索引;
- 数据字典;
- 锁信息;
缓冲池管理
为了提高缓存管理的效率,缓冲池被实现为页链表,使用LRU算法来进行管理,与传统LRU算法不同的是,InnoDB存储引擎对LRU做了一些优化。当需要向缓冲池中添加新页时,将淘汰最近最少使用的页,并将一个新页添加到列表的midpoint位置,而不是放在首部。这种中点插入策略
将LRU划分为两个子列表:
- New Sublist:在midpoint前面,new子列表,即热点页;
-
Old Sublist:在midpoint后面,old子列表。
如图所示,new子列表占用5/8的空间,midpoint正处于列表3/8的位置,old子列表占用3/8的空间。可以通过innodb_old_blocks_pct
(默认37,按百分比算=3/8)配置midpoint的位置。
算法逻辑:假设用户读取某个缓冲池中没有的页。
- 先去磁盘读取页到缓冲池;
- 该页对LRU来说是一个新的页,因此会把它加到midpoint,也就是old子列表的首部;
- 如果该页在之后的某段时间被访问了,LRU算法会认为它是热点页,将它加到new子列表的首部,这个操作被称为
page made young
;
InnoDB还提供一个配置参数innodb_old_blocks_time
延迟新插入的页,被加到new子列表的首部的时间。比如说,假设设置了innodb_old_blocks_time=1000
,这个时候有个新页被加到midpoint位置,在1000ms期间,无论该页被访问多少次,都没办法被加到new子列表的首部。这个操作被称为page not made young
。
为什么InnoDB不使用传统的LRU算法?答案很明显,假设某张学生表有1000个学生,现在通过
select * from student where name = '张三'
,这条sql做了学生表的全表扫描,innoDB会将扫到的页都存放到缓冲池中,也就是加到LRU。如果是传统LRU,则扫描到一个页,就加到首部。因此把大量的页(可能这些页只用一次,后面就不再使用了)放到了首部,导致原先热点数据被挤出来。
查看缓冲池的运行状态
可以通过SHOW ENGINE INNODB STATUS;
查看。
参数名称 | 描述 |
---|---|
Total memory allocated | 分配给缓冲池的总内存,以字节为单位 |
Dictionary memory allocated | 分配给InnoDB数据字典的总内存,以字节为单位 |
Buffer pool size | 分配给缓冲池的页的总大小 |
Free buffers | 缓冲池空闲列表中页的总大小 |
Database pages | 缓冲池LRU列表中的页的总大小 |
Old database pages | 缓冲池LRU旧子列表中的页的总大小 |
Modified db pages | 缓冲池中当前修改的页数 |
Pending reads | 等待读入缓冲池的缓冲池页数 |
Pending writes LRU | 缓冲池中从LRU列表底部等待写入的旧脏页数量 |
Pending writes flush list | 在检查点期间要刷新的缓冲池脏页的数目 |
Pending writes single page | 缓冲池中等待写入的独立页数量 |
Pages made young | 在缓冲池LRU列表中变为年轻的页面总数 |
Pages made not young | 缓冲池LRU列表中未变为年轻的页面总数 |
youngs/s | 变年轻的频率 |
non-youngs/s | 没有变年轻的频率 |
Pages read | 从缓冲池中读取的页面总数 |
Pages created | 在缓冲池中创建的页面总数 |
Pages written | 从缓冲池写入的页面总数 |
reads/s | 从缓冲池中读取的页面频率 |
creates/s | 在缓冲池中创建的页面频率 |
writtes/s | 从缓冲池写入的页面频率 |
Buffer pool hit rate | 缓冲池页命中率 |
young-making rate | 页面访问导致页面年轻的平均命中率 |
not (young-making rate) | 页面访问没有导致页面年轻的平均命中率 |
Pages read ahead | 预读操作的每秒平均次数 |
Pages evicted without access | 被淘汰,而无法从缓冲池中访问页面的每秒平均数量 |
Random read ahead | 随机预读操作的每秒平均次数 |
LRU len | 缓冲池LRU列表中的页的总大小 |
unzip_LRU len | 缓冲池解压缩LRU列表中页的总大小 |
I/O sum | 最近50秒内访问缓冲池LRU列表页的总数 |
I/O cur | 访问缓冲池LRU列表页的总数 |
I/O unzip sum | 最近50秒内访问缓冲池解压缩LRU列表页的总数 |
I/O unzip cur | 访问缓冲池解压缩LRU列表页的总数 |
从上面可知,‘Free buffers’代表的是缓冲池空闲列表中页的总大小,当数据库启动时,LRU的列表是空的,即没有任何页。这时,页都放到Free List中,当需要从缓冲池中分配页时,首先从Free List中查找是否有空闲页,如果有则从Free List移到LRU 列表中;否则,根据LRU算法,淘汰末尾的页,腾出来给新的页用。
其次,‘Pending writes flush list’表示脏页列表的总页数,在LRU列表中的页被修改后,成为脏页。Flush list是来管理脏页列表,负责将脏页刷回磁盘。当然LRU列表也会存储在脏页,保证缓冲池页的可用性,两者并不冲突。
另外,有一个重要的参数‘Buffer pool hit rate’命中率等于100%,说明缓冲池运行状态非常好,通常来讲,该值不应该小于95%,如果发生这种情况,考虑是否是因为全表扫描引起的LRU被污染的问题,可以适当调整innodb_old_blocks_time
大小,或者避免全表扫描。
最后,看下‘LRU len: 5720, unzip_LRU len: 0’。‘LRU lenInnoDB’代表LRU列表页的总数量(包含unzip_LRU),‘unzip_LRU’代表解压缩(说明被压缩过了,才需要解压缩)LRU列表中页的总大小。缓冲池页的大小默认是16K,对于非16K(原本16K的页压缩为1K,2K,4K,8K),是通过unzip_LRU列表进行管理。unzip_LRU管理方式如下:假设需要申请一个4K的页。
- 检查unzip_LRU列表是否有空闲的4K页,有则直接使用;如果没有,则往下走;
- 检查是否有空闲的8K页,有则切割成两个4K页,一个直接使用,一个存放到unzip_LRU列表;如果没有,则往下走;
- 从LRU列表申请一个16K的空闲页,切割成一个8K,两个4K,分别存放到unzip_LRU列表中。