春招MySQL知识点总结提炼版(个人向)
InnoDB Buffer Pool
InnoDB Buffer Pool是以数据页为粒度管理数据的。解压页的大小是16KB,这些页由页的控制体和页构成,存储着用户数据,索引,锁的数据,页的控制体存储着数据页的状态信息。InnoDB对数据文件读取的最小粒度也是页。InnoDB提供了数据压缩功能。如果这个页是压缩页,则它的大小有1KB,2KB,4KB,8KB,16KB。如果一个页的压缩后大小超过压缩页大小,B+树将会进行分裂。
InnoDB Buffer Pool使用LRU算法管理页,同时缓存压缩页和解压页。当Free List大小不够时,系统将要驱逐已经缓存的页,InnoDB Buffer Pool会根据当前IO压力决定是否需要驱逐压缩页,如果紧张,则只驱逐解压页。在一个页中,数据不一定有序,但是保证页和页之间range不会重叠。
InnoDB Buffer Pool管理所有页面的控制体的数据结构叫Buffer Chunks,它在数据库启动时按照最大的页数大小分配,直到数据库停止运行时才回。通过这个数据结构可以访问所有数据页,除了没有被解压的压缩页和对应的解压页已经被修改过的且已经被驱逐的压缩页。数据页控制体会保存一个指向数据页的指针,数据页由伙伴系统进行分配。
InnoDB Buffer Pool中管理不同的数据页控制体的数据结构是多个链表。分别有:
-
Free List
节点中放的是尚未分配有实际数据页的控制体,如果一个数据页需要加载入内存中,可以直接从上面获取一个控制体,如果Free List没有可用的控制体了,就需要从Flush List和LRU list中刷盘一个数据页并得到一个数据页控制体了。 -
Flush list
这个链表指向的数据页都是已经被修改过等待写入磁盘的数据页,一个数据页有可能有多次修改,但是会记录最早的一次修改的LSN(log sequence number)。LSN越小越靠后,刷盘时总是先刷链表尾部,即最早被修改的页。 -
LRU list
所有数据页都会通过LRU算法管理,最近最少使用的结点被放在链表的尾部。同时LRU list尾部还会放一些还没被解压的压缩页。LRU list的前是young list,存放经常使用的热点页,后是old list,存放冷数据和新读入LRU list的数据,直到过了一段时间之后数据仍使用,才放入young list,这主要是防止预读和全表扫描污染。此外,解压页存储在另一个Unzip LRU list中。因为Buffer Pool在驱逐时会驱逐压缩页和解压页。
数据页控制体
数据页控制体包括buf_block_t和buf_page_t。buf_block_t第一个元素是buf_page_t,第二个元素是指向数据页的指针,buf_block_t上还存储了Unzip LRU list的头指针和页锁block_mutex_t。buf_page_t主要存放buf_state_t表示页面状态等信息,buf_fix_count和io_fix主要用于并发控制。加锁正在读取的数据页不能被驱逐,buf_fix_count就是用来记录这样的页的数量,io_fix用来记录当前页的操作,如果buf_fix_count不为零且io_fix不为BUF_IO_NONE,则表明此时存在一个数据页在IO操作,就不能驱逐,这样子可以减少页锁的争抢。
磁盘读流程
- 一个MySQL进程会有多个Buffer Pool实例,所以我们查找这个页在哪个Buffer Pool实例中。,page_no的六位被砍掉,这是为了保证一个extent(一个extent是连续的64个页,大小1M)的数据能被缓存到同一个Buffer Pool Instance中,便于后面的预读操作。
- 在page hash找到这个页是否加载,如果没有,并且读取的mode是BUF_GET_IF_IN_POOL_OR_WATCH则设置watch数据页,接下来,如果没有找到数据页且mode为BUF_GET_IF_IN_POOL、BUF_PEEK_IF_IN_POOL或者BUF_GET_IF_IN_POOL_OR_WATCH函数直接返回空,表示没有找到数据页。如果mode是其他模式,就读取磁盘,在读取磁盘数据之前,我们如果发现需要读取的是非压缩页,则先从Free List中获取空闲的数据页,如果Free List中已经没有了,则需要通过刷脏来释放数据页获取到空闲的数据页后,加入到LRU List中。
- 如果发现需要读取的是压缩页,则临时分配一个buf_page_t用来做控制体,通过伙伴系统分配到压缩页存数据的空间,最后同样加入到LRU List中。
- 读取数据成功后,我们需要判断读取的数据页是不是压缩页,如果是的话,因为从磁盘中读取的压缩页的控制体是临时分配的,所以需要重新分配block,把临时分配的buf_page_t给释放掉,接着进行解压,解压成功后,设置state为BUF_BLOCK_FILE_PAGE,最后加入Unzip LRU List中。我们判断这个页是否是第一次访问,如果是则设置access_time。
- 如果mode不为BUF_PEEK_IF_IN_POOL,我们需要判断是否把这个数据页移到young list中。如果mode不为BUF_GET_NO_LATCH,我们给数据页加上读写锁。如果mode不为BUF_PEEK_IF_IN_POOL且这个数据页是第一次访问,则判断是否需要线性预读。
获取空闲数据页的流程
- 查看Free List中是否还有空闲的数据页,如果有则直接返回。
- 如果Free List中已经没有空闲的数据页了,则会尝试驱逐LRU List末尾的数据页。如果Unzip LRU List大于LRU List的十分之一或者当前InnoDB IO压力比较大,则会优先从Unzip LRU List中把解压页给驱逐,否则会从LRU List中把解压页和压缩页同时驱逐。
- 如果在上一步中没有找到空闲的数据页,则需要刷脏,用户线程最多刷一个脏页,防止刷过多的脏页阻塞用户线程。
- 如果上一步的刷脏因为数据页被其他线程读取而不能刷脏,则重新跳转到上述第二步。进行第二轮迭代,与第一轮迭代的区别是,第一轮迭代在扫描LRU List时,最多只扫描特定个数,而在第二轮迭代开始,扫描整个LRU List。如果很不幸,这一轮还是没有找到空闲的数据页,从三轮迭代开始,在刷脏前等待10ms。
- 最终找到一个空闲页后,page的state为BUF_BLOCK_READY_FOR_USE。
刷脏时由于有可能有多个线程同时刷脏,所以一个线程刷完之后有可能它前面的脏页被另外的线程刷了,导致每次刷脏都要重新在链表尾部找到新的脏页,这里用了一个优化,就是在锁的保护下确定目前那个脏页还没被刷,然后直接把指针调整到该页。
Double Write Buffer
操作系统的单次写入只有4KB,而InnoDB Buffer Pool一个页大小是16KB,因此如果在写入页的时候数据库异常停机,则有可能导致页不完整。InnoDB Buffer Pool引入双写缓冲区,写数据时除了写入数据页外还写入dblwr,然后当dblwr写满时强制刷盘写入公共空间,数据页再异步刷盘,如果出现页不完整的情况,可以使用dblwr恢复。
InnoDB Log
Redo Log
Redo Log是InnoDB中WAL,它先于数据落盘,一般在写入Redo Log Buffer就进行落盘。在数据库崩溃后,Redo Log的重放可以恢复数据库到崩溃前的状态。Redo Log的重放需要保证页对应和幂等,前者通过dblwr中的页标志和磁盘中的页标志对应确定页,后者通过记录在页上的LSN实现,Redo时会把该页的LSN改成Redo时的LSN,如果某一页的LSN是Redo的LSN则不重放。
Redo Log在事务写入是产生,一次事务操作产生了多条操作都会记录在Redo Log,当事务结束时会在Redo log打事务完成标记,在恢复的时候如果某个Redo Log没有结束标记,则这个事务的所有操作都不重放。已经提交的事务的脏数据如果在数据页全部刷盘之后,这段Redo Log就会失效,为了不让Redo Log无限增长,需要删除无用的Redo Log,InnoDB会周期性的检查Redo Log中涉及的页在Buffer Pool中最小的LSN,这是尚未加入Flush List的页,和已经加入Flush List的页的最小LSN,两者取最小作为Checkpoint写入Redo Log中,在Checkpoint前的Redo Log可以删除。
Undo Log
Undo Log在InnoDB中用于并发控制和故障恢复。Undo Log以逻辑日志的形式存在,且作为数据一样需要写Redo Log。
Undo Log在内容上分成两种:Insert Undo Record和Update Undo Record.
Insert Undo Record记录了一条Insert操作之前的数据库状态,不作为MVCC使用,只要记录Record的Key即可。
Update Undo Record记录了一条Update和Delete操作之前的数据库的状态,在MVCC中,如果一个版本还在使用,则不能删除,所以后来的删除会为这个数据打上一个删除的标记,如果重新Update则取消删除标记。Update Undo Record会记录数据的增量,然后通过rollptr和上一版本的Undo Record连接在一起。同时因为需要知道这个Undo Record的事务号以确定其他事务的可见性。
Undo Log在事务主动回滚,死锁恢复和故障恢复中使用时,重启之后,Undo Log的Redo Log会先进行重放,恢复Undo Log,然后从后向前扫描Undo Log回滚未提交的事务,然后完成了回滚的Undo Log会被回收。
Undo Log也可以在MVCC中使用,它通过一个rollptr指针把一条数据的多个版本串成了一个链表,然后这个链表记录着数据的变化,事务通过自己的事务号可以知道记录的可见性,然后读取对应版本的数据直到可以恢复出所有的数据然后返回,同时保证了不能读取正在活跃和未执行的写事务。
Undo Log清理的时机有两种情况,一种是Undo Log本身清理,一种是Undo Log上对应的数据需要清理。前者是,当事务已经提交之后,事务就保证了即使数据库崩溃也不会出现丢失,此时不再需要Undo Log了,我们就可以清除Undo Log。或者是,一个写事务在所有的读事务之前就提交(即写事务的trx_no小于所有活跃事务的最小trx_no),这时读事务可以看到最新的数据,就不需要Undo Log了,我们也可以清除Undo Log。此时我们会把Undo Log按照事务号从小到大放到History List上然后一次遍历回收。打上了删除标记的Undo Record,因为有可能还会有其他事务在访问它的旧版本,所以Purge线程会沿着rollptr恢复出所有数据的信息然后从二级索引和主键索引上删除。然后InnoDB会遍历History List中删除Undo Log并释放磁盘空间。
ARIES算法
ARIES算法是借助Redo Log和Undo Log在崩溃时恢复数据库的一种算法。MySQL使用一种叫做Fuzzy Checkpoint机制把脏页写入至数据库中。这种机制是根据IO压力自适应选择写入磁盘的脏页数防止阻塞其他需要磁盘IO的线程,同时每写入一批文件,Checkpoint时会把当前活跃的未提交事务和脏页写入Redo Log中。
在数据库崩溃时,假设还有脏页没有写到磁盘中,这些修改将会丢失。在数据库重新恢复时,由于引入了Checkpoint机制,InnoDB会按LSN从小到大重放Redo Log,重放后的Redo Log的LSN会修改成重放时的LSN,此时所有记录在Redo Log上的已提交或者未提交的事务都被恢复了。通过Redo Log可以找出所有未提交的事务,这些事务将使用Undo Log进行回滚。脏页则在Redo Log中找到最早的那个脏页所对应的日志,并以此为起点进行Redo Log重放。此时可能会遇到的Redo Log对应的数据页实际已经写入至磁盘中了,因为Redo Log是幂等的,所以即使再次重放也不影响。假设发现了数据页半写,缺失了Redo Log所需要的字段,如果开启了dblwr,则可以先用dblwr的数据恢复页面,再进行Redo Log重放。
InnoDB事务和锁
Reference
https://www.cnblogs.com/mchxyz/p/16043587.html
https://www.cnblogs.com/defectfixer/p/15835714.html
https://blog.csdn.net/weixin_34123613/article/details/92562478
https://www.cnblogs.com/mchxyz/p/16043587.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)