一、底层数据结构-页(Page)
为了避免一条一条读取磁盘数据(IO操作频繁),减少磁盘与内存交互的成本,InnoDB采取页的方式,作为磁盘与内存交互的基本单位,基本大小一般为16KB。InnoDB为了不同的目的设计了不同类型的页,eg:存放表空间头部信息的页、存放undo日志信息的页、存放表中数据记录的页(索引页、数据页)。
1、InnoDB数据页结构(User Record 与Free Space此消彼长关系):
不同阶段页的结构:
2、记录(可以理解为插入的数据)的头信息
1>deteled_flag(逻辑删除)
删除操作:把deteled_flag=1;
将删除的记录,组成一个垃圾链表(目的:空间可重用)。
不直接采用物理删除的原因是减少性能消耗。
2>min_rec_flag
B+树种每层非叶子节点中标识最小目录项记录。
3>n_owned
将一个页划分为若干个组,一个组里的主键最大的保存该值,该值标识一个组里面有多少条记录。
4>heap_no(堆号)
每条插入的的记录都会分配堆号,从2开始(InnoDB默认该页最小值Infimum heap_no = 0,该页最大值Supremum heap_no = 1)。
5>record_type(记录类型)
0 普通记录
1 B+树非叶子节点目录项记录(叶子节点存放数据为普通记录)
2 表示最小值的记录
3 表示最大值的记录
6>next_record(下一条记录)
从当前记录的真是数据指向效益条记录的真实数据。位置如下图:
指向该位置的原因:向前可查询记录的额外信息,向后可查询记录的真实数据。
3、Page Directory
记录在页中的体现,存储方式为组,使用槽号去快速标识。
1>最底层记录的特点:
按照主键从小到大依次排序;单向链表。
2>分组规则
对于页中最小记录只能有一条,即自己;
对于页中最大记录,阻力只能有1-8条记录;
对于其他记录阻力只能有4-8条。
3>分组步骤
最初,无记录,一个新的数据页分为两个组,Infimum组一条记录,Supremum组一条记录;
之后,插入记录到Supremum组,n_owned +1;
最后,Supremum组满,进行拆分为4+5两个组,申请新的槽位号。
4>页中的删除操作
5>页满后的插入操作,插入分页时也会保证主键的排序
实际操作中为了避免因保证主键有序而产生的数据迁移操作,可采用增加一个自增的列作为主键的方法。
二、索引(Index)
1、B树和B+树
在单页中进行某条数据查询时,使用槽位号根据最大值最小值二分法,再单项遍历去查询,那涉及到多个页时怎么办呢,这时就要涉及到B+树。
B树和B+树的区别(引自https://www.cnblogs.com/nullzx/p/8729425.html):
2、索引
引用B+树结构,叶子节点存储完整的数据页,非叶子节点存储页目录项(包含页的最大值和最小值)
,搜索时也按照二分法去查找。
1>聚 簇 索 引(主键索引)
2>二级索引:查找到二级索引后再根据主键索引去回表查询
3>联合索引(二级索引的一种),与二级索引的区别在于多列,会默认有个排序的列索引,查找方式为:先根据排序的列索引去查找,再去查找另外一列,如果不按照排序的列索引去查找,只能一个个去查找。如果索隐列无法确定一个目录项,则会默认添加主键索引。
三、缓冲池(Buffer-Pool)
索引–>数据–>页–>磁盘。每次查询都要把该记录所在的页里的所有记录都加载,若查询的记录不在同一个页中,那需要加载更多的记录,会产生大量的IO操作。磁盘与内存交互会限制查询速度,为加快交互,引入缓冲池。
缓冲池概述:
1>free链表:控制块的双向链表,决定是否还有空间去插入缓冲页
2>flush链表:存放被修改的缓冲页(脏页)对应的控制块
什么时候会将脏页刷新到磁盘?
A:
BUF_FLUSH_LIST:从flush链表中刷新一部分脏页到磁盘,后台有线程的情况下,根据系统繁忙程度确定刷新速率。
BUF_FLUSH_SINGLE_PAGE:系统很繁忙,导致刷新脏页的速率很低,没有可用的缓冲页,就会去查看LRU链表尾部,看是否存在可以直接被释放的没有被修改的缓冲页,如果没有,则不得不将LRU链表尾部的一个脏页刷新到磁盘中。
BUF_FLUSH_LRU:从LRU链表的冷数据刷新一部分页面到磁盘。
3>LRU链表:控制块的双向链表,解决预读和全表扫描问题
预读:
线性预读–>顺序访问读取当前区读取超过56个,预读下个区全部页;
随机预读–>当前区13个连续的页被加载,预读加载当前区所有页(该功能默认不开启)。
全表扫描:
查询时未加条件或未加索引,都会导致全表扫描,会冲刷热数据。
4>缓冲池空间分配很大时,会分为多个缓冲池实例,每个事例中有多个chunk,以便在重新分配内从空间时方便拷贝。
如有不对地方,欢迎指出,共同学习!