1.Page页
1.1读取方式
- 为了避免一条一条读取磁盘数据,InnoDB采取页的方式,作为磁盘和内存之间交互的基本单位。一个页的大小一般是16KB。
1.2记录
- 每页中有多条记录,其中的Free Space和User Records就是记录此消彼长的关系
- 每条记录分为真实数据与额外数据,额外数据中的头信息比较重要
当中记录着:- 当前该数据是否被删除
- 索引的目录项
- 每个组的记录条数
- 在堆中的相对位置
- 当前记录的类型
- 下一条记录的位置信息为一个指针
1.3Page Directory(分组)
- 当数据源量大时通过page进行查询也比较耗时,所以将每页的记录也进行分组,提升查询速度
- 其中每页包含Infimum和Supremum也就是最小的分组和最大分组
- 初始情况只有最小和最大两个分组,随着记录增加分组也会进行增加,Supremum会进行分裂,Infimum分组只有一条记录
- slot分组会指向每个分组“老大”的那个位置
- 比如要查询一个值,会通过该值的主键与slot分组中的“老大”的主键进行比较,若小于该分组最大主键值,则再进行该组中的遍历
- 等于为页中的每条记录打包了很多个数组,找到所属的组后,再进行组内遍历,上图中说过,每个组最多只有8条记录所以查询效率非常高,在数据量大时能更快进行查找定位数据
1.4总结
- mysql中数据存储单位为:
page页 >>> page Directory分组 >>> 记录(额外/真实信息)- page页为InnoDB数据库引擎作为磁盘和内存之间交互的基本单位,一个页的大小一般是16KB
- page Directory分组,为解决数据量大时查询遍历慢,所以将记录进行打包存储方便快速定位数据进行查询及操作
- 记录为数据最小单位,每条记录存储真实数据与额外数据,将所有数据用指针进行串联
2.B树 & B+树
3.Index索引
- 当记录越来越多,创建的页也会越来越多,如果仅通过链表方式遍历查询,性能会出现很大问题。如何解决呢? 采用B+树结构,即:叶子节点里存储完整的数据(数据页),非叶子节点存储主键索引(索引页)
- 索引即数据,数据即索引
3.1聚簇索引(主键索引)
- 主键索引树结构中
- 非叶子结点:存储每页的最小主键值与页码值
- 叶子结点:存储记录的真实数据,主键索引存储该记录的所有数据
- 下图对应蓝色与黄色中存储的record_type与next_record
3.2二级索引(非主键)
- 二级索引树结构中
- 非叶子结点:存储每页的最小主键值与页码值
- 叶子结点:存储记录的真实数据,二级索引存储该记录当前索引列的值与主键值,若还需要查询别的字段需要通过主键值进行回表查询
3.3联合索引(多列)
- 联合索引包含了多列数据,不过需要使用最左匹配原则,如两个查询条件c2=1,c3=2此时才会有效,若只有c3=2一个条件则匹配不上,c3列需要单独加索引
- 联合索引与二级索引查询方法类似,只有当第一个条件匹配并且有多个时,第二条件才会生效
- 但联合索引值不宜过多,因为查询效率虽然高但是若增删记录时由于索引列过多导致效率变低
3.4总结
- 只有主键索引的叶子节点中才包含记录的所有数据,二级索引只记录主键和当前列索引的值,当使用二级索引查询时会获取到符合条件的记录,若需要查询二级索引中不包含的内容(select * from …),则需要通过二级索引中的主键进行回表,在主键索引中进行查询数据,为防止回表,尽量避免使用select * 进行查询
4.Buffer Pool缓冲池
- Buffer Pool(缓冲池)为一片连续的内存空间,默认为128M
- Buffer Pool对应的一片连续的内存被划分为若干个页面,默认也是16KB,该页面称为缓冲页
- 为了更好的管理Buffer Pool中的这些缓冲页,InnoDB为每个缓冲页都创建了控制块,它与缓冲页是一一对应的
4.1Free链表
- Buffer Pool缓冲池分为控制块 & 缓冲页,为了能知道哪些缓冲页为空闲的可以使用,所以将空闲的缓冲页对应的控制块部分进行串连组成Free链表(空闲可用的空间)
4.2Flush链表
- 修改了缓冲页中的数据后该页为脏页,为了性能并不会立即刷到磁盘内,flush链表则是将这部分脏页的控制块部分串连组成Flush链表(脏数据空间)
4.3LRU链表
- LRU链表为最近使用的链表,会将最近访问的数据插入到LRU链表到头部,由于Buffer Pool的空间只有128M(默认)比较有限,以若空间满了后,会将LRU尾部的数据进行更替。
- LRU链表分为两个区域,young热数据(默认为63%)与old冷数据区(默认为37%),这两个区域的划分与后面的预读和全表扫有关
4.3.1预读与全表扫
- 线性预读:
如果顺序访问某个区(extent,一个区默认64个页)的页面超过了innodb_read_ahead_threshold(默认56)的值,就会触发一次异步读取下一个区中全部的页到Buffer Pool中的请求- 随机预读:
如果开启了随机预读功能(默认:innodb_random_read_ahead=OFF),如果某个区(extent)有13个连续的页面都已经被加载到了Buffer Pool中,无论这些页面是不是顺序读取的,都会触发一次异步读取本区全部的页到Buffer Pool中的请求- 全表扫:
全表扫字面意思,将表中的所有数据进行查询加载
问题:触发预读或全表扫后,会对加载的数据进行访问,那么第一次访问的时候,就会将该页放到young区域的头部。这样仍然会把那些使用频率比较高的页面给“排挤”下去,破坏之前维护的热数据
- 针对预读的优化
InnoDB规定,当磁盘上的某个页在初次加载(只是加载,没有涉及读取)到Buffer Pool中的某个缓冲页时,该缓冲页对应的控制块会被放到old区域的头部。这样预读页就只会在old区域,不会影响young区域中使用比较频繁的缓冲页- 针对全表扫描的优化
由于全表扫描有一个特点,就是它对某个页的频繁访问且总耗时很短。所以,针对这种情况,InnoDB规定,在对某个处于old区域的缓冲页进行第一次访问时,就在它对应的控制块中记录下这个访问时间,如果后续的访问时间与第一次访问的时间在某个时间间隔内(即:innodb_old_blocks_time,默认为1000,单位为ms),那么该页面就不会从old区域移动到young区域的头部,否则将它移动到young区域的头部。
4.4脏页数据同步到磁盘
从flush链表中刷新一部分页面到磁盘
- 后台线程也会定时从flush链表中刷新一部分页面到磁盘,刷新的速率取决于当时系统是否繁忙。——即:BUF_FLUSH_LIST
- 有时后台线程刷新脏页的进度比较慢,导致用户准备加载一个磁盘页到Buffer Pool中时没有可用的缓冲页。此时,就会尝试查看LRU链表尾部,看是否存在可以直接释放掉的未修改缓冲页。如果没有,则不得不将LRU链表尾部的一个脏页同步刷新到磁盘(与磁盘交互是很慢的,这会降低处理用户请求的速度)。——即:BUF_FLUSH_SINGLE_PAGE
从LRU链表的冷数据中刷新一部分页面到磁盘
- 后台线程会定时从LRU链表的尾部开始扫描一些页面,扫描的页面数量可以通过系统变量innodb_lru_scan_depth来指定,如果在LRU链表中发现脏页,则把它们刷新到磁盘。——即:BUF_FLUSH_LRU
- 控制块里会存储该缓冲页是否被修改的信息,所以在扫描LRU链表时,可以很轻松地获取到某个缓冲页是否是脏页的信息。
5.Redo Log
问题:如果我们只在内存的Buffer Pool中修改了页面,假设在事务提交后突然发生了某个故障,导致内存中的脏页还未同步到磁盘中,那么这个已经提交的事务在数据库中所做的更改也就丢失了。针对这种问题,怎么处理呢?
- 方案1:在事务提交时,把该事务修改的所有页面都刷新到磁盘,刷新成功了才提示事务提交成功
这样做会有两个问题:
- 1.刷新一个完整的数据页太浪费了,虽然我们只修改了一条记录,但是会将这条记录所在的页(16KB)都刷新到磁盘上,会造成大量磁盘I/O的浪费。
- 2.随机I/O刷新起来比较慢,一个事务可能包含很多语句,即使是一条语句也可能修改许多页面,并且该事务修改的这些页面可能并不相邻。这就意味着将某个事务修改的Buffer Pool中的页面刷新到磁盘时,需要进行很多的随机I/O。而随机I/O要比顺序I/O慢,尤其是机械硬盘。
- 方案2:在事务提交时,只需要把修改的内容记录一下就好了
例如:“将第0号表空间第100号页面中偏移量为1000处的值更新为2。”- 如此记录后,一旦发生服务出现故障等原因,导致一个事务已经提交,并写入了Buffer pool缓冲池中,还未同步到磁盘中的情况发生时,可以借助Redo log将数据通过原子性恢复。
- 所有redo logo日志从log buffer中同步到磁盘,都是具备原子性,当事务提交后,提示该事务成功时,则说明已经将redo日志同步至磁盘中了。若日志在log buffer同步至磁盘时,突然出现断电等意外,由于没有完成原子性操作,则会通过undo log对当前事务进行回滚。
5.1Redo日志类型
- 分为简单日志和复杂日志,比如insert操作,只是在页中插入一条数据则比较简单,若涉及到索引、页内数据迁移等就需要用到复杂日志类型
5.2MTR日志组
- 一个日志组可以理解为一个MTR,一个事务会有多条SQL语句,一条SQL语句会有多个MTR,一个MTR会有多个redo日志
- 事务 >>> SQL >>> MTR >>> redo
5.3Log Buffer(redo日志缓冲区)
- 由redo组成的MTR存在block中,block与page(页)的概念类似,log buffer由多个block组成,log buffer与buffer pool类似都在内存中申请了一段空间用来进行缓存
5.4Redo日志刷盘机制
5.5LSN
5.6CheckPoint
6.事务