1.缓存池Buffer Pool结构
buffer pool组成:数据页(或者说缓存页)、索引页index page 、插入缓存insert buffer 、锁信息、自适应哈希索引、数据字典信息。
查看innodb存储引擎信息命令:
show engine innodb status;
buffer pool默认大小128M,查看默认大小
show variables like 'innodb_buffer_pool_size';
可以用命令进行修改:
set global innodb_buffer_pool_size = 2147483648;
数据库的缓存池buffer_pool可以有多个实例:
show variables like 'innodb_buffer_pool_instances';
Buffer Pool实例大小等于innodb_buffer_pool_size/innodb_buffer_pool_instances;
2.各种链表
2.1 free链表
free双向链表记录的是空闲可用的数据页。他由描述数据块组成的双向链表,头节点记录着链表的大小。每个描述数据块又指向数据页的地址。
在数据库启动初始化时,数据页应该都为空。当从磁盘加载数据页时,需要从free链表找空闲数据页,将数据页写入buffer pool中的数据页中,同时将该数据描述节点从free中移除。
如何知道数据页有没有被缓存呢?
由一个哈希的数据结构用来存放那些数据页被加载了。key=表空间+数据页号 value=数据页地址。
2.2 flush链表
我们知道mysql数据更新,是先undo log日志,接着更新缓存池中的数据,之后提交事务写redo log 、binlog日志才会将数据刷新到磁盘。那么那些数据需要刷新到磁盘呢?于是引入了flush链表。flush链表是数据页的描述数据块的指针,让被修改过的数据页的描述数据块组成双向链表。flush链表几乎呵free链表一样。用来记录那些数据页是脏页。
2.3 lru链表
我们知道buffer pool中的数据页是从磁盘加载的,buffer pool空间毕竟有限,很多数据页加载到数据页之后必然占用很多空间,导致无法继续加载其他需要的数据。这时就涉及淘汰一些数据页,而淘汰那些数据页,通过lru链表来记录。
lru就是最近最少使用算法。lru链表也是通过描述数据块组成的双向链表,将最近最常使用的数据页的描述数据块放在链表的头部,最近最少使用的放在链表尾部。当尾部的最近被使用过会放入链表的头部,也就是从后往前。
mysql对lru算法进行了改进,引入了midpoint中间点,当需要向缓冲池中添加新页时,将淘汰最近最少使用的页,也就是lru链表的尾部节点,并将一个新页添加到列表的midpoint位置,而不是直接放在首部。midpoint位置并不是位于链表中间,而是距离头部3/8的位置。
可用通过命令查看:
show variables like 'innodb_old_blocks_pct';
默认37。
3.Buffer Pool 如何解决并发请求
Buffer Pool由多个实例组成,每个buffer pool负责管理一部分缓存页和描述数据块,每个buffer pool 有自己得free、flush、lru链表。当有多个线程并发访问,就可以由不同得buffer pool处理。buffer pool是基于chunk组成,每个chunk默认大小128MB。
如果在进行crud的时候,数据库频繁的发没有空闲的缓存页,那么后台线程就会去lru链表冷数据链找最近不经常使用的缓存页,然后将数据刷新到磁盘腾出空闲缓存页。然后再将需要使用的数据页加载到buffer pool。这个过程需要至少进行两次磁盘IO,会影响性能。所以本质上缓存页是一边被使用,一遍被清除掉。如果使用缓存页的速度快,后台线程释放缓存页的速度慢,最终必然会频繁发现缓存页被使用完了,而缓存页的使用速度没发控制,除非进行限流,访问数据块的并发程度决定缓存页的使用情况。后台定时释放一批缓存页这个过程不好进行优化。如果释放的过于频繁,磁盘IO也会频繁,也不利于提高数据库的性能。最关键的还是buffer pool的大小。
一台机器内存大小有8G、16G、32G、64G、128G等,一般设置buffer pool的大小为机器内存大小的50%-60%就可以了。这样确定了buffer pool总大小后,应该考虑设置多少个buffer pool实例。一个很关键的公式:buffer pool总大小 = (chunk大小*buffer pool数量)*N倍 也就是说是128的整数倍。
你需要根据机器的内存大小合理的配置buffer pool的大小和个数,尽可能保证数据库的高并发和高性能。