MySQL学习之InnoDB结构探秘

MySQL
285
0
0
2023-09-11
目录
  • 内存结构
  • Buffer Pool
  • Redo log buffer
  • ​Double Write Buffer
  • 磁盘文件
  • 表空间
  • 重做日志文件

InnoDB架构图

内存结构

Buffer Pool

why buffer pool ?

InnoDB是基于磁盘存储,其存储的最基本单元是页,大小为16KB。而CPU和磁盘之间速度相差悬殊,所以通常使用内存中的缓冲池来提高性能。

what is buffer pool ?

缓冲池里主要缓存了:

  • data page 数据页
  • index page 索引页
  • insert buffer
  • what is insert buffer ?
  • 插入缓冲, 用于将多个insert操作合并成一个,减少IO开销,提高插入性能。只能用在非唯一的辅助索引
  • why ?
  • 插入主键索引时是顺序的,不需要随机IO,速度会很快。但对于非唯一的辅助索引,插入的叶子节点是分散的,需要离散的访问索引页。
  • how does it work ?
  • 对非唯一辅助索引,索引的修改并非实时更新索引树的叶子节点,而是把若干个对同一页的更新操作缓存起来,合并为一次操作,从随机IO转为顺序IO,减少IO次数,提高写入性能。
  • 流程:
  • 先判断要更新的索引页,是否在缓冲池中
  1. 若是,直接插入
  2. 若不是,则存入Insert Buffer,后交给Master Thread 来进行合并
  • extend :
  • 一开始只针对INSERT操作,所以叫insert buffer,后来能够支持 INSERT/UPDATE/DELETE,所以改叫change buffer了
  • 若是唯一索引,在插入时需要检查索引列的值是否存在,故在修改索引之前,需要把相关的索引页读出来,去判断是否唯一,这时Insert Buffer就失效了。
  • lock info 锁信息
  • data dictionary 数据字典(主要包括了一些元数据信息,如表结构信息)
  • adaptive hash index 自适应hash索引,InnoDB为热点页建立的索引,用以提高查询效率

how does it work ?

数据库的读操作,会先判断欲读取的页是否在缓冲池中,若是,则命中缓冲;否则,从磁盘上读取,并将读取到的页放入缓冲池。

数据库的写操作,是先修改缓冲池中的页,再以一定频率,将缓冲池刷新到磁盘。将数据从缓冲池刷新到磁盘,是通过一种checkPoint的机制完成的

practice ?

可通过配置参数innodb_buffer_pool_size来设置缓冲池大小

可以看到本机的mysql缓冲池大小为134217728B,换算后是128MB

Redo log buffer

why redo log buffer ?

当缓冲池中的页是脏页(修改数据时,是先修改缓冲池中的数据,此时缓冲池中的数据和磁盘上不一致,称为脏页)时,需要通过某种机制将脏页刷新到磁盘。若缓冲池中一有数据页发生改变,就马上刷新磁盘,效率会很低。所以InnoDB采用Write Ahead Log策略,事务提交时,先将redo log写入磁盘,这样就认为脏页已经写入到磁盘了。之后再通过checkpoint机制择时将脏页真正写入磁盘,脏页真正写入磁盘后,就可以删掉对应的redo log了。若脏页还没写入磁盘,发生了宕机,则由于redo log已经成功写入磁盘,故可以通过redo log进行数据恢复。

redo log保证了事务的持久性。写redo log时,先将redo log放入redo log buffer,再将redo log按一定策略刷新到磁盘,这是通过innodb_flush_log_at_trx_commit参数来配置的,参数名在不同的mysql版本或许有不一样,通过如下命令可以查看:

show variables like 'innodb_flush%';

innodb_flush_log_at_trx_commit参数配置有3个取值:0,1,2 其含义如下图所示

innodb_flush_log_at_trx_commit属性可以控制每次事务提交时InnoDB的行为。当属性值为0时,事务提交时,redo log被写到redo log buffer,然后等待主线程按时写入;当属性值为1时,事务提交时,会将redo log写入文件系统缓存,并且调用文件系统的fsync,将文件系统缓冲中的数据真正写入磁盘存储,确保不会出现数据丢失;当属性值为2时,事务提交时,也会将日志文件写入文件系统缓存,但是不会调用fsync,而是让文件系统自己去判断何时将缓存写入磁盘。

当参数值为0时,写入效率最高,但是数据安全最低;参数值为1时,写入效率最低,但是数据安全最高;参数值为2时,二者都是中等水平。一般建议将该属性值设置为1,以获得较高的数据安全性,而且也只有设置为1,才能保证事务的持久性。

redo log buffer的大小可以通过innodb_log_buffer_size去控制

​Double Write Buffer

双写缓冲,和磁盘文件中系统表空间里的Double Write Segment一起解决了页的部分写入失效问题。

MySQL数据库IO的最小单位是16KB,文件系统(File System)IO的最小单位是4KB,磁盘IO的最小单位是512B。(具体的大小可能有差异,但意思就是这么个意思,MySQL的基本存储单位和文件系统的基本存储单位大小不一致)。由于MySQL的1个单位(页),相当于文件系统中的4个单位,在将内存中的脏页刷新到磁盘时,一页会分4次进行写入,若在写入过程中发生意外情况,比如断电,宕机,则可能成功写入2次,即写入了8KB,那另外8KB还是旧的数据,这叫做部分写失败,导致这一页的数据,一半是新的,一半是旧,数据不完整,成为坏页,最终数据不一致。此时redo log 也无能为力,因为redo log记录的是对物理页的修改操作,此时页本身已经损坏,再对损坏的页应用修改操作,也无法恢复为完整数据。

Double Write就是为了解决这个问题。

Double Write 由2部分组成

  1. 内存中的Double Write Buffer(2M)
  2. 磁盘共享表空间中的Double Write Segment(2M)

它的工作机制是这样的

  1. 当触发了脏页刷新时,脏页并不直接写入磁盘文件,而是先拷贝到内存中的Double Write Buffer
  2. 接着将Double Write Buffer,分两次写入到共享表空间中,每次写1MB
  3. 最后再将Double Write Buffer中的脏页数据,写入到实际的各个表空间里,写入完成后,即标记对应的Double Write 数据可被覆盖

若发生意外宕机等情况,先从共享表空间中取出Double Write数据,复制到表空间中,再应用redo log,这样即完成了数据恢复。

优点

提高了数据的可靠性

缺点:

由于Double Write 实际是一个物理文件,即是一个file,它会导致操作系统进行更多的fsync刷盘操作,所以它会降低mysql的性能。然而,double write buffer往磁盘写的时候是顺序写入,性能很高。

可以关闭Double Write的场景

  1. 频繁的DML
  2. 不担心数据损坏和丢失
  3. 系统主要负载集中在写操作

简单来说,就是加了一个中间层,先将要写入磁盘的数据,暂存到一个中继站,成功存到中继站之后,再进行实际的写磁盘,写完磁盘后,再告诉中继站说,刚才暂存到你那里的数据我已经落盘了,你可以把它标记为无用数据了。相当于拿这个中继站做了数据的保险。如果在实际写磁盘时,发生意外,那么损坏的数据块,我可以从中继站那里拿到一份完整的拷贝,保证了数据的完整性。

磁盘文件

表空间

  • 系统表空间(共享表空间 ibdata1)
  • 它是被多个表共享的,可以通过innodb_data_file_path参数对系统表空间进行配置
# 格式
innodb_data_file_path = datafile[,datafile2]
# 可以指定多个文件,共同组成系统表空间
innodb_data_file_path = /db/ibdata:1000M;/db/ibdata2:1000M
# 设置了这个参数后,所有基于InnoDB的表,都会被存储到系统表空间
  • 数据字典(各种元数据,如表结构的定义)
  • Double Write Buffer
  • Insert Buffer (Change Buffer)
  • undo log
  • 在系统表空间创建的表数据,索引数据
  • 用户表空间(独立表空间 ibd)
# 开启独立表空间
innodb_file_per_table =
# 设置这个参数后,每个表都是一个单独的 .ibd 文件

我本机的MySQL是默认开启了这个参数

所以都是一个表一个ibd文件

但是独立表空间里,只存了数据,索引,插入缓冲bitmap。其余的信息还是存在系统表空间。

重做日志文件

redo log :

一般InnoDB的数据目录下,会有2个名为ib_logfile0和ib_logfile1的文件,这就是redo log文件

每个InnoDB引擎的表至少要有1个重做日志组(group),一个group下至少有2个重做日志。为了得到更高的可靠性,用户可设置多个镜像日志组

InnoDB根据checkpoint对2个文件进行循环写入。

可通过innodb_log_file_size设置redo log的大小。若设置太大,数据丢失时,恢复可能要花很长时间;若设置太小,则会导致checkpoint进行频繁检查,并将脏页刷新到磁盘,导致性能抖动。

重做日志的落盘机制在上面的redo log buffer里已经说明,简单总结起来就是2个机制:Write Ahead Log + Force Log at Commit

这两者保证了事务的持久性

一次写操作的事务流程如下图所示:

若发生了崩溃,则恢复数据的过程如下图所示:

binlog是维护在SQL Layer层的,故不包含在InnoDB中。