buffer pool 的内部原理和结构
行记录
变长字段长度列表:存储变长字段的长度,如varchar、text、blob
null值列表:记录各列是否为null 1:null 0:非null
隐藏列 ROW_ID: 没有创建主键 并且没有唯一索引的时候会默认创建 rowId,,否则不创建
trx_id:事务的id
roll_ptr:回滚的id
记录头信息:5个字节
delete_mark :删除标志
min_rec_mark: B+树每层非叶子节点中最小的记录会记录本值
n owned:一个槽中索引最大的行会存储此值,记录这个槽中有多少条记录
heap_no:记录在页的位置
record_type: 0:普通记录、1:非叶子节点、2:最小记录、3:最大的记录
next_record:下一条记录的地址,在一个数据页中记录的是单向列表,下一条记录 不是按照插入顺序记录的 是按照键值的大小排序的。
行记录的格式分为:compect 、dynamic、compress、redundant
各个格式之间的区别
redundant:5.0以前的版本
compect:对于数据溢出,一页存不下的行,可存储前768的数据,后面用20个字节指向溢出页的地址
dynamic:会把所有的字段都存储到溢出页,只记录溢出页的地址
compress;采用压缩算法对页面数据进行压缩,对于溢出数据同dynamic的处理方式
页
File header: 38个字节,存储的是页面的编号、上一页 下一页、存储验证码,各种类型的页面通用的。
File trailer: 8个字节 前四个字节存储和fileheader 一致的验证码,后四个字节存储修改时对应的日志位置。
page head: 56个字节 ,存储该页面又多少行记录、多少槽、第一条记录的地址是多少
user records:存储的是用户数据,存储的是一个链表,当一行记录被删除了,delete_mark被标记成1,从该链表中删除,增加到可复用的链表中,而不是回收这块空间,因为回收空间会引起页面的分裂重新排序浪费性能
infimum:+ supremum:26个字节 内容是0 本页数据中的最小值
supremum:本页数据中的最大值 内容是1
page directory:记录每个槽中最大记录的偏移地址,为了解决一个页面通过链表查询数据慢的情况,引入了槽的概念,使用槽先进性二分查找,找到具体的槽之后 进行链表遍历,变得链表最大长度为8,类似于hashmap,
free space:未被占用的空间
一个页用16k的大小
区
在屋里内存上是一块连续的空间,写入的时候是顺序io,包含64个页,也就是一个区有1个M的空间
组
每256个区被划分成一组,表空间的第一个组用于存储一些整体属性,每组开始的两个页面用于存储256个区的一些属性
段
可分为叶节点段,非叶子节点段,回滚段,用于全表扫描可剩更少的内存
innobd的磁盘空间分布
独立表空间
用于存储.idb文件,如果innodb_file_per_table 为ON则存储在表空间 否则 存储在通用表空间
系统表空间
存储了双写缓冲区、数据字典的系统表等
共享表空间
临时表空间
双写缓冲区(doublewrite buffer)
buffer pool中也有一份,2个区,也就是2M。innnodb是按照页尾单位写入磁盘的,每个页16kb,但是磁盘写入的单位 每个扇区一般是4k,所以写入一页要分4次写入,如果在写入磁盘的过程中发生了宕机情况就会造成数据页损坏;
等到下次重启的时候,因为redolog是存储的更改逻辑,需要结合原数据进行数据合并后得到正确的结果,单数数据页损毁原数据不在,所以这种情况不能通过redolog进行修复。
即引入了双写缓存区, 先写在磁盘的双写缓冲区里,在写入独立表空间。如果表空间的的数据页损毁 可通过双写缓冲区进行恢复;
如果双写缓冲区也损毁 则通过redolog 进行恢复,因为此时表空间的原始数据没有损毁;
因为需要两次io 所以开启了双写缓冲区 会增加性能的消耗 一般在5%~10%之间。
开启双写缓冲区参数:innodb_doublewrite
数据字典
SYS_TABLES 整个InnoDB存储引擎中所有的表的信息
SYS_COLUMNS 整个InnoDB存储引擎中所有的列的信息
SYS_INDEXES 整个InnoDB存储引擎中所有的索引的信息
SYS_FIELDS 整个InnoDB存储引擎中所有的索引对应的列的信息
SYS_FOREIGN 整个InnoDB存储引擎中所有的外键的信息
SYS_FOREIGN_COLS 整个InnoDB存储引擎中所有的外键对应列的信息
SYS_TABLESPACES 整个InnoDB存储引擎中所有的表空间信息
SYS_DATAFILES 整个InnoDB存储引擎中所有的表空间对应文件系统的文件
change buffer/insert buffer
是bufferpool中的一块空间,默认占25%,磁盘中持久化放在系统表空间里
是针对二级索引更改的优化,是更改逻辑的缓存。不着急进行io写入,等数据进行访问、空闲时刻、正常关机的情况下 进行merge,
redolog
undolog
buffer pool 的结构
是mysql 启动时像os申请的一块空间
存储数据、change buffer、redolog
show status like "%Innodb_buffer_pool_pages%";
8192(Innodb_buffer_pool_pages_data+Innodb_buffer_pool_pages_free+Innodb_buffer_pool_pages_misc)*16(每个数据页16k)/1024=128 (
show variables like "%innodb_buffer_pool_size%";
)
Innodb_buffer_pool_pages_data:占用的数据页
Innodb_buffer_pool_pages_dirty:脏页
Innodb_buffer_pool_pages_flushed:被刷的脏页
Innodb_buffer_pool_pages_free:空闲页
Innodb_buffer_pool_pages_misc:其他系统数据占用的页
Innodb_buffer_pool_pages_total:buffer pool的总页数
buffer pool的内存大小
show variables like "%innodb_buffer_pool_size%";
展示的为字节数,是128M,在使用单独的服务器时,官方建议设置成内存的80%,但是还有一些额外的内存可能会占用到90%,所以一般是60%~75%左右,这个值由DBA根据系统业务高峰期 内存占用率具体设置
buffer pool的free list
内存会分成一个个的页大小的空间,并增加对应的控制块(这个控制块并不占用buffer pool的申请的内存)
控制块中包括该页所属的表空间编号、页号、缓存页在Buffer Pool中的地址、链表节点信息、一些锁信息以及LSN信息,当然还有一些别的控制信息。
没有被使用的页的控制块组成的双向列表称为 free list;
当需要寻找寻找一页数据页看是否在缓存中时 适应表空间号+页号当做key 在hash列表中可以找到对应的页,如果没有需要去磁盘中加载
buffer pool的flush list
脏页对应控制块组成的双向列表称为 flush list
简单的LRU list的算法规则
用内存就需要用到内存淘汰的机制,
LRU :least recently used 每次访问被访问的数据就添加到列表的头部。慢慢的不经常的访问的数据就会被过滤到尾部,淘汰时先从尾部淘汰
预读
INNODB 根据计算机的局部性原理 会预读数据
线性预读:当访问的页超过 56(innodb_read_ahead_threshold)就会把整个区读入缓存
局部预读:当一个区中访问的页超过13个 无论些是不是连续的 就会把整个区读入缓存 默认该功能是关闭的 innodb_random_read_ahead
预读和全表扫描的情况下简单的LRU list的产生的后果
全表扫描数据量大 势必会淘汰一部分之前的内存,其中会包含热数据
预读的数据会放在优先访问的位置,使原来的热数据后移,如果预读的数据不会被使用 则大大降低了缓存的命中
buffer pool的LRU改进点
将LRU list 分为两部分 前部分为热数据 成为 yong区,后面为冷数据 成为old区;
innodb_old_blocks_pct 默认是37 也就是old区占比3/8
新缓存进来的页放入old区
设置old区页面访问的间隔,SHOW VARIABLES LIKE 'innodb_old_blocks_time'; 默认是1s 也就是说old区的页面第一次访问和最后一次访问的时间间隔不到一秒 会默认是全表扫描的页面 不加入yang区;
对于yong区的数据 后面四分之一的数据被击中才需要移到前面
刷脏页
多个buffer pool
show variables like "%innodb_buffer_pool_instances%"; 默认是1,设置多个buffer pool的实例可以增加并发的性能,最小是1 最大的64,如果每个实例内存分配不到1个G则强制为1;
内存分配innodb_buffer_pool_size/innodb_buffer_pool_instances
chunk
5.7.5之后的版本可以在系统运行时设置buffer pool的大小;
为了不要把原内存的数据重新复制到新的空间 引入了chunk的概念,默认是128M innodb_buffer_pool_chunk_size;
以chunk为单位向系统申请空间
SHOW ENGINE INNODB STATUS\G buffer pool的系统状态
Total memory allocated:代表Buffer Pool向操作系统申请的连续内存空间大小,包括全部控制块、缓存页、以及碎片的大小。
Dictionary memory allocated:为数据字典信息分配的内存空间大小,注意这个内存空间和Buffer Pool没啥关系,不包括在Total memory allocated中。
Buffer pool size:代表该Buffer Pool可以容纳多少缓存页,注意,单位是页!
Free buffers:代表当前Buffer Pool还有多少空闲缓存页,也就是free链表中还有多少个节点。
Database pages:代表LRU链表中的页的数量,包含young和old两个区域的节点数量。
Old database pages:代表LRU链表old区域的节点数量。
Modified db pages:代表脏页数量,也就是flush链表中节点的数量。
Pending reads:正在等待从磁盘上加载到Buffer Pool中的页面数量。当准备从磁盘中加载某个页面时,会先为这个页面在Buffer Pool中分配一个缓存页以及它对应的控制块,然后把这个控制块添加到LRU的old区域的头部,但是这个时候真正的磁盘页并没有被加载进来,Pending reads的值会跟着加1。
Pending writes LRU:即将从LRU链表中刷新到磁盘中的页面数量。
Pending writes flush list:即将从flush链表中刷新到磁盘中的页面数量。
Pending writes single page:即将以单个页面的形式刷新到磁盘中的页面数量。
Pages made young:代表LRU链表中曾经从old区域移动到young区域头部的节点数量。
Page made not young:在将innodb_old_blocks_time设置的值大于0时,首次访问或者后续访问某个处在old区域的节点时由于不符合时间间隔的限制而不能将其移动到young区域头部时,Page made not young的值会加1。
youngs/s:代表每秒从old区域被移动到young区域头部的节点数量。
non-youngs/s:代表每秒由于不满足时间限制而不能从old区域移动到young区域头部的节点数量。
Pages read、created、written:代表读取,创建,写入了多少页。后边跟着读取、创建、写入的速率。Buffer pool hit rate:表示在过去某段时间,平均访问1000次页面,有多少次该页面已经被缓存到Buffer Pool了。
young-making rate:表示在过去某段时间,平均访问1000次页面,有多少次访问使页面移动到young区域的头部了。
not (young-making rate):表示在过去某段时间,平均访问1000次页面,有多少次访问没有使页面移动到young区域的头部。
LRU len:代表LRU链表中节点的数量。
unzip_LRU:代表unzip_LRU链表中节点的数量。
I/O sum:最近50s读取磁盘页的总数。
I/O cur:现在正在读取的磁盘页数量。
I/O unzip sum:最近50s解压的页面数量。
I/O unzip cur:正在解压的页面数量。
自适应hash