概述
缓冲池简单来说就是一块内存区域,通过内存的速度来弥补磁盘速度较慢对数据库性能的影响。写入时,先将数据写入缓冲池种,再定期刷新到磁盘;读取时,将读到的页放到缓冲池种,下次再读取相同的页时,会首先判断该页是否在缓冲池中,若再缓冲池中则直接从缓冲池中返回。
从InnoDB 1.0.X版本开始,允许有多个缓冲池实例,每个页根据哈希值平均分配到不同缓冲池实例中,这样做的好处是减少数据库内部的资源竞争,增加数据库的并发处理能力,可以通过参数innodb_buffer_pool_instances来进行配置。
mysql> show variables like 'innodb_buffer_pool_instances'\G;
*************************** 1. row ***************************
Variable_name: innodb_buffer_pool_instances
Value: 1
1 row in set (0.00 sec)
将参数调为2,运行show engine innodb status\G; 可看到2个缓冲池已开启(这里遇到一个坑,只改instances参数,试了很多遍还是只有一个缓冲池,后来发现将缓冲池大小调整为1G以上才生效)
----------------------
INDIVIDUAL BUFFER POOL INFO
----------------------
---BUFFER POOL 0
Buffer pool size 40960
Free buffers 40420
Database pages 537
Old database pages 218
Modified db pages 0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 0, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 466, created 71, written 77
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 537, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
---BUFFER POOL 1
Buffer pool size 40960
Free buffers 40520
Database pages 436
Old database pages 0
Modified db pages 0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 0, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 363, created 73, written 81
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 436, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
缓冲池页大小
再InnoDB存储引擎中,缓冲池中页的默认大小为16KB,从1.0.X版本开始支持压缩页的功能,将原本16KB的页压缩为1KB、2KB、4KB、8KB。对于非16KB的页,是通过unzip_LRU列表进行管理的,通过show engine innodb status可以看到如下内容:
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 537, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
LRU中的页包含unzip_LRU列表中的页,假如需要申请页为4KB的大小,步骤如下;
- 检查4KB的unzip_LRU列表,检查是否有可用的空闲页,若有,则直接使用
- 否则,检查8KB的unzip_LRU列表,若有,则将页分成2个4KB页,存放到4KB的unzip_LRU列表
- 若还不能得到空闲页,从LRU列表申请一个16KB的页,将页分成1个8KB和2个4KB,分别存放到对应unzip_LRU列表中
缓冲池LRU算法
InnoDB存储引擎对传统的LRU算法做了一些优化,LRU列表中加入了midpoint位置。新读取到的页,虽然是最新访的页,但并不是直接放到LRU列表的首部,而是放到LRU列表的midpoint位置。这个算法在InnoDB存储引擎下称为midpoint insertion strategy。在默认配置下,该位置在LRU列表长度的5/8处,即百分之37,可由参数innodb_old_blocks_pct控制。
mysql> show variables like 'innodb_old_blocks_pct'\G;
*************************** 1. row ***************************
Variable_name: innodb_old_blocks_pct
Value: 37
1 row in set (0.13 sec)
上面可看到默认为37,表示新读取的页插入到LRU列表尾端的百分之37的位置。把midpoint之后的列表称为old列表,之前的列表称为new列表。
这么做是因为若直接将读取到的页放入到LRU的首部,那么某些SQL操作可能会使缓冲池的页被刷新出去,从而影响缓冲池的效率。常见的这类操作为索引或数据的扫表操作,这类操作需要访问列表中的很多页,甚至是全部的页,而这些页又仅仅在这次操作中需要,并不是活跃的热点数据。如果直接放到LRU首部,那么肥场可能将其他热点数据从LRU全部移除,而在下一次需要读取该页时,InnoDB引擎需要再次访问磁盘。
同时,InnoDB存储引擎还引入了另一个参数innodb_old_blocks_time,用于表示页读取到mid位置后需要等待多久才会被加入到LRU列表的热端。
mysql> mysqlvariables like 'innodb_old_blocks_time'\G;
*************************** 1. row ***************************
Variable_name: innodb_old_blocks_time
Value: 1000
1 row in set (0.00 sec)
当页通过LRU列表的old部分进入到new部分时,会记录youngs,而因为超时没有进入到new部分,会记录not young
Pages made young 6448526, not young 0
48.75 youngs/s, 0.00 non-youngs/s
Pages read 5354420 created 239625, written 3486063
55.68 reads/s, 81.74 creates/s, 955.88 writes/s
Buffer pool hit rate 1000/1000, young-making rate 0/1000, not 0/1000
non-youngs/s的值过大原因
- 可能存在严重的全表扫描,频繁的被刷出来
- 可能是pct设置的过小,冷数据区很小,来一点数据就刷出去了
- 可能是time设置的过大,没坚持到,被刷出去了
youngs/s的值过大的原因,正常不可能一直很高,因为热数据区就那么大,不可能一直往里调。
- pct过大
- time过小
如果缓存命中率过低,需要排查是否由于全表扫描引起的LRU列表被污染的问题。