MySQL知识点总结(六)------InnoDB底层架构
- InnoDB底层架构总览
- InnoDB底层各组件分析
-
- [Buffer Pool](#Buffer Pool)
- [Change Buffer](#Change Buffer)
- [Log Buffer](#Log Buffer)
- [Adaptive Hash Index](#Adaptive Hash Index)
- [System Tablesapce](#System Tablesapce)
- [Undo Tablespaces](#Undo Tablespaces)
- [Redo Log](#Redo Log)
InnoDB底层架构总览
关于InnoDB底层架构,网上有一张非常经典的图:
首先InnoDB从整体上分为两大块,一块是内存结构,一块是磁盘结构。
内存结构方面主要有"Buffer Pool"、"Log Buffer"、"Adaptive Hash Index",其中"Buffer Pool"中还包含了一个"Change Buffer"要特别注意。
- Buffer Pool是InnoDB的内存缓存池,通过Buffer Pool缓存查询结果所在的数据页,下次查询时可以在Buffer Pool中对应的数据页直接取得查询结果,无需再走磁盘IO。
- Change Buffer是Buffer Pool的一部分,InnoDB把数据页的更新缓存到Change Buffer,避免每次SQL增删改都通过磁盘IO更新磁盘数据页,提高SQL增删改的性能。
- Log Buffer是InnoDB的redo log在内存中的缓存,由于InnoDB不会立刻把已被修改的数据页刷到磁盘中,为了避免更新的数据丢失,需要用redo log作为预写日志。
- Adaptive Hash Index是InnoDB默认开启的功能,通过hash算法快速定位缓存到Buffer Pool中的数据页。
而磁盘结构则主要是各种表空间tablespace,以及Redo Log日志文件。
Redo Log 很好理解,就是内存中的log buffer持久化到磁盘中的日志文件。
而这里的各种表空间,就有点多,主要包含5类表空间:"System Tablesapce"、"File-Per-Table Tablespaces"、"General Tablespaces"、"Undo Tablespaces"、"Temporary Tablespaces"。
- System Tablesapce:系统表空间,全局只有一份,存储Doublewrite Buffer、Change Buffer、Undo Logs等数据。
- File-Per-Table Tablespaces:我们可以设置我们的每个表自己单独存放在独立的表空间,此时我们每创建一个表,InnoDB都会为我们自动创建一个表空间,每个表空间单独一个".ibd"文件。老版本的MySQL默认是不开启的,但是从5.6.6版本开始,默认是开启的。
- General Tablespaces:通用表空间,就是我们通过create tablespace显式创建的表空间,我们创建表的时候可以指定使用这些表空间。
- Undo Tablespaces:存放undo log的表空间。
- Temporary Tablespaces:临时表空间。
以上是MySQL 5.7版本的,MySQL 8.0版本做了一些改动。
内存结构是没有变化的,还是保持原来的模样,而磁盘结构做了一定的调整。
首先是系统表空间System Tablesapce,做了简化,只保留Change Buffer的持久化内容,其他的东西都去掉了(System Tablesapce里面的东西会在下面介绍),或者从系统表空间中移出去了。
然后就是增加了一个"Doublewrite Buffer Files",这个就是从系统表空间中移出来的东西(Doublewrite Buffer也会在下面介绍)。
以上是对InnoDB底层架构从宏观角度上的一个介绍,接下来我们一一对其中每个组件进行分析。
InnoDB底层各组件分析
Buffer Pool
Buffer Pool是InnoDB自带的缓存,用于缓存由于SQL执行的增删改查而读取到内存中的数据页。下次再有查询需要读取相同的数据页时,就可以直接从缓存中读取。
首先我们要明白一点,MySQL是以页(16KB)为单位进行读取,也就是即使我们查询的是一行数据,MySQL也会读取一个页到内存中,也就是磁盘中该行记录所在的数据页,这是基于局部性原理的一个设计。
比如我们要查询id为1的一条记录,InnoDB就会查找到id为1的记录所在的数据页,把它加载到内存中,Buffer Pool就会把该数据页缓存起来,当我们下次再读取id为1的记录,或者与之相邻的同在一个数据页中的其他记录,就无需再从磁盘中把该数据页读到内存,而是直接在该页中查找并返回,这样就提升了查询性能。
Buffer Pool以链表的形式存放加载进来的数据页,Buffer Pool有三个链表:Free List,LRU List,Flush List。
Free List 是空闲链表,Buffer Pool中最开始只有Free List,而LRU List和Flush List都是空的。MySQL一启动,InnoDB就会向操作系统申请内存作为Buffer Pool的空间,然后将该内存空间分成固定大小16KB的一个一个的缓存页,存放在Free List中。
当执行SQL时,需要加载某个数据页到内存中,就会从Free List中拿出一个缓存页存放加载到内存中的数据,该数据页存放在LRU List中。LRU List 顾名思义,是使用了LRU算法的一个链表,当Free List中已经没有空闲的缓存页可以分配时,LRU List就会根据LRU算法把旧的缓存页淘汰出Buffer Pool,然后再把腾出的空间给最新加载进来的数据页使用。
如果某个被加载进Buffer Pool的页面被修改,它就会被挪到Flush List,此时它就变成一个脏页,等待被InnoDB刷到磁盘。
这里的LRU List有一点要注意,它使用的LRU算法不是经典的LRU算法,而是经过改进的LRU算法。如果按照经典的LRU算法,那么每个页被访问一次,就要把它放到头部,但如果这样的话,就会存在全表扫描时数据量过大导致冷数据把热点数据挤出的问题。
因此改进后的LRU算法把LRU链表分成两部分,一部分是Young区域,默认占LRU List大小的5/8,另一部分是Old区域的3/8。当内存不足需要淘汰一部分LRU List中的缓存页时,就会优先淘汰Old区域的缓存页。
当一个数据页首次被访问时,会被加载到LRU List的Old区,如果该缓存页最后一次被访问的时间距离首次被访问的时间超过了1秒,就会被挪到Young区,否则还是会继续呆在Old区。之所以这么设计,是因为每扫描一行记录,都算作是对该页的一次访问,在全表扫描的情况下,就会对一个数据页访问很多次,因此需要设置一个1秒的时间间隔,只有第一次与最后一次访问的时间间隔超过1秒,才移到Young区域中。
Change Buffer
当我们向MySQL服务端提交一条update语句时,InnoDB就要对相应的数据页做修改。如果该数据页已缓存到了Buffer Pool,那InnoDB可以直接修改缓存页,然后把它移到Flush List,异步刷盘,再加上Redo Log的预写日志机制,即能保证更新不丢失,又能提升性能。但是如果要修改的数据页不在Buffer Pool中的话,是不是就要马上读到内存中呢?显然不是,如果这样做,那么InnoDB的写性能就会很低,因为从磁盘读取数据页是一个随机读的操作,磁盘的随机读性能是很低的,因此InnoDB就搞了个Change Buffer,用于缓存更新操作,它也是Buffer Pool的一部分。
InnoDB先把对没有加载到Buffer Pool中的数据页的修改(增删改)操作缓存到Change Buffer中,等到下次接收到读取该数据页的命令时,该数据页加载到Buffer Pool后,就把Change Buffer中缓存的对该数据页的修改操作应用到该缓存页中,这个操作叫merge操作。
缓存在Change Buffer中的更新操作也会写入到redo log中,因此不会发生更新丢失的情况。
Log Buffer
Redo Log会缓存在内存中的Log Buffer中,Redo Log默认是每次提交事务时就刷盘,但是我们也可以配置为异步刷盘。
Adaptive Hash Index
上面介绍的Buffer Pool会缓存从磁盘中加载到内存的数据页,下一次查询读取的数据如果位于相同的数据页时,由于Buffer Pool已经缓存了该数据页,因此无需再从磁盘中读取。那问题是?InnoDB怎么知道Buffer Pool中是否存在当前查询需要读取的数据页呢?
答案就是通过"Adaptive Hash Index","Adaptive Hash Index"意思是"自适应哈希索引",是InnoDB自带的一个索引,默认开启,不可关闭。
Adaptive Hash Index 以表空间号加页号作为key,查找对应的数据页是否已经缓存在了Buffer Pool中,如果缓存命中,则直接返回。缓存不命中,再去磁盘中读取。
以上都是内存结构中的组件,下面我们对磁盘结构中的组件进行分析。
System Tablesapce
系统表空间分为四部分:"InnoDB Data Dictionary"、"Doublewrite Buffer"、"Change Buffer"、"Undo Logs"。
- InnoDB Data Dictionary:InnoDB数据字典,存储的是元数据信息的表,比如sys_tables、sys_columns、sys_indexs、sys_fields等的一些系统表。
- Doublewrite Buffer:双写缓冲区,这个下面专门介绍。
- Change Buffer:Buffer Pool里面的Change Buffer,持久化后存储到这里。
- Undo Logs:Undo Logs默认的存储位置,当然也可以配置存储到Undo Log专门的表空间中。
上面的四个部分,除了Doublewrite Buffer是没有介绍过的,其他一看就知道是干嘛的,下面介绍一下Doublewrite Buffer的作用。
因为InnoDB一页的大小是16KB,当InnoDB刷脏页到磁盘时,每刷一个脏页,等于是往磁盘写16KB的数据,InnoDB必须保证要么16KB全部刷写成功,要么就全部失败,不能刷到一半就GG,比如刷了8KB,然后MySQL挂了,那么这一个页就废了,前面8KB是刷脏页刷进来的数据,后面8KB是原来的数据。
因此,Doublewrite Buffer的作用就是保证脏页刷到磁盘时的"原子性"。InnoDB刷脏页时不直接刷到磁盘的数据页上,而是先刷到Doublewrite Buffer中。当脏页成功刷到Doublewrite Buffer之后,那么这个脏页就持久化到磁盘,即使MySQL挂了,重启也能取到这个脏页的数据。如果刷到Doublewrite Buffer成功,那么再写入到磁盘数据页中,否则就算是刷脏页失败,也能保证我们的数据页是正确的,只是该数据页是没更新数据之前的,此时可以通过redo log重做一遍,然后重写刷脏页即可。
我们看下刷脏页失败的两种情况:
- 刷Doublewrite Buffer失败
- 刷Doublewrite Buffer成功,写数据页失败
第一种情况,MySQL重启后,可以通过redo log恢复出脏页,然后再重新刷一遍Doublewrite Buffer。
第二种情况,MySQL重启后,根据Doublewrite Buffer再写一次磁盘数据页就可以了。
Undo Tablespaces
Undo Tablespaces是Undo Log的表空间集合,InnoDB默认不会写入到Undo Tablespaces里面的表空间,而是默认写入到System Tablespace系统表空间的Undo Logs区域,只有当我们配置了写到Undo Log自己的独立表空间时,才会写入到Undo Tablespaces里面。
但是在MySQL8.0以上的版本,做了改进,从System Tablespace中移除了Undo Logs,默认存储到Undo Tablespaces中。
Redo Log
InnoDB中的redo log是重做日志,用于保障事务的持久性,当事务提交时,首先写入到内存结构中的Log Buffer区域,然后默认会立刻持久化到磁盘中,持久化到磁盘中时就会存储在磁盘结构中的Redo Log,磁盘结构中的Redo Log指的是磁盘中的redo log文件。
redo log文件的文件名一般是ib_logfile0、ib_logfile1这种固定的格式。并且redo log文件不是无限增长的,而是固定大小,固定文件个数的,当写到最后一个文件并且写满之后,就会回到第一个文件开头,覆盖式的写入,相当于是一个环状结构。