当前位置: 首页 > 科技观察

面试官问我InnoDB的物理存储结构!

时间:2023-03-22 10:41:57 科技观察

前段时间去面试,面试官突然问我:说说InnoDB的物理存储结构吧!物理结构,但我真的了解不多!那么,今天就来说说InnoDB的物理存储结构吧!相信很多人都知道逻辑结构和物理结构这两个概念,但是又很好奇它们有什么区别?简单的说:所谓物理存储结构就是指MySQL的数据在物理介质上是如何存储的,由哪些磁盘文件组成。所谓逻辑存储结构是指数据是如何以结构化的方式组织起来的。本文介绍的InnoDB物理存储结构是基于MySQL8.0的。从MySQL官方文档中我们知道InnoDB在物理层面上可以分为以下七个模块:系统表空间(SystemTablespace)、双写缓冲文件(DoublewriteBufferFiles)、撤销表空间(UndoTablespaces)、重做日志文件(RedoLog)独占表空间(File-Per-TableTablespaces)通用表空间(GeneralTablespaces)临时表空间(TemporaryTablespaces)在这七个模块中,最关键的是以下三个模块:系统表空间(SystemTablespace),独立表空间(File-Per-TableTablespaces),日志文件组(RedoLog)。InnoDBDiskStorageStructureMySQL8.0SystemTablespace在开始之前,我们需要了解表空间的概念。其实表空间就是存放表的空间的意思。是InnoDB用来描述数据存储的术语,大致意思就是存储表相关数据的地方。系统表空间,顾名思义,就是InnoDB系统默认的表空间,也叫共享表空间,对应MySQL数据目录下的ibdata1文件。如果我们将innodb_file_per_table设置为off,那么我们使用CREATETABLE语句创建的表数据将存储在系统表空间中。而如果我们将innodb_file_per_table设置为on,那么每张表都会独立生成一个表空间文件,以ibd结尾,表的数据、索引、内部数据字典信息都会保存在这个单独的表空间文件中。但是有些信息,比如undolog信息,还是会保存在系统表空间中。比如我们在test数据库中创建了一张名为user的表,那么在mysql数据文件夹的test文件夹下就会有一个名为user.ibd的文件。我们注意到系统表空间还有一个叫做更改缓冲区的东西。它是什么?Changebuffer,其实在之前的版本中是insertbuffer,在后续的版本中变成了changebuffer(参考:Insider2.4.1Section)。该区域是更改缓冲区。5.5之前叫InsertBuffer来插入buffer,现在也可以支持delete和update了。最后将ChangeBuffer记录到数据页的操作称为merge。这里有一句很关键的话:除了主键聚合索引,还会生成name列的辅助索引。对于这个非聚集索引,叶节点的插入不再是有序的。这时候就需要对非聚集索引进行离散访问。页,插入性能变低。这句话的意思是:聚簇索引的插入是有序自增的,所以插入的时候可以直接跟在后面,不需要其他的随机访问操作。由于插入的非聚集索引列没有顺序,所以可能插入到中间。这时候就需要检查中间页是否加载到内存中了。如果加载了,没关系,那就做B+树操作,调一下就好了。如果不在内存中就麻烦了,需要多次将页读取到内存中。为了提高效率,我创建了一个插入缓冲区。简单的说,就是先缓存多个插入或修改操作,等到时间差不多了,再一起做,提高效率,避免多次IO读取。插入缓冲区不是缓存的一部分,而是物理页面。对于非聚集索引的插入或更新操作,并不是每次都直接插入索引页。而是先判断插入的非聚集索引页是否在缓冲池中。如果是,则直接插入,如果不是,则先放入一个插入缓冲区。然后以一定的频率对插入缓冲区和非聚集索引页的子节点进行合并操作。当一个数据页需要更新时,如果数据页在内存中,则直接更新,如果数据页不在内存中,InnoDB会在不影响数据一致性的情况下,将这些更新操作缓存在changebuffer中,这样这个数据页不需要从磁盘读取。当下次查询需要访问这个数据页时,将数据页读入内存,然后在changebuffer中执行与这个页相关的操作。DoublewriteBufferFiles如果说insertbuffering给InnoDB存储引擎带来了性能,那么doublewrite给InnoDB存储引擎带来了数据可靠性。当数据库宕机时,可能是数据库正在写一个页面,而这个页面只写了一部分(比如一个16K的页面,只写了前4K页),我们称之为写失败。在InnoDB存储引擎没有使用双写之前,会出现部分写失败导致数据丢失的情况。InnoDB存储引擎双写架构如下图所示:双写缓存(doublewrite)示意图双写由两部分组成:一部分是内存中的双写缓冲区,大小为2MB。另一部分是物理磁盘上共享表空间中的128个连续页,也就是两个区域,大小也是2MB。刷新缓冲池中的脏页时,并不会直接写入磁盘,而是先通过memcpy函数将脏页复制到内存中的doublewritebuffer中,然后通过doublewritebuffer分成两份,并且每次写入1MB到share表空间的物理磁盘上,然后立即调用fsync函数同步磁盘,避免bufferedwrite带来的问题。在这个过程中,因为doublewritepages是连续的,所以这个过程是顺序写的,开销不是很大。doublewritepage写入完成后,将doublewritebuffer中的page写入到各个表空间文件中,此时的写入是离散的。我们可以通过showglobalstatuslike'innodb_dblwr%'\G来观察具体情况。所以双写其实就是在flushdirtypages的时候,dirtypage的数据会被写到共享表空间的两个page中,同时也会被写到特定的表空间中。共享表空间中的数据起到备份作用。如果操作系统在将页面写入特定表空间时崩溃,在恢复过程中,InnoDB存储引擎可以从共享表空间中的doublewrite中找到该页面的副本,将其复制到表空间文件中,然后Applyredologs.UndoTablespaces(撤消表空间)撤消Log的数据默认在系统表空间ibdata1文件中,因为共享表空间不会自动收缩,也可以单独创建一个撤消表空间。关于回滚段和撤消日志的一些问题?很多时候,我们不明白。为什么有人说undolog在系统表空间,而官方图片有undotablespace?下面这段说的很清楚了,原因是Versionchanges!回滚段(rseg)称为回滚段。在Mysql5.6之前,undo默认记录在系统表空间(ibdata)中。如果启用了innodb_file_per_table,它会被放在每个表的.ibd文件中。5.6以后可以创建独立的undo表空间,8以后默认开启独立的undo表空间。最少2个,保证至少有1个undotablespace被truncate,继续使用1个undotablespace。每个回滚段默认有1024个undolog段,mysql5.5后一个undotablespace支持128个回滚段。0号回滚段默认在系统表空间ibdata中,1-32回滚段在临时表空间中,33~128在独立的undo表空间中(如果不开启,则在系统表空间ibdata中,所以系统tablespace会太大),因此一个表空间最多支持96*1024个事务,超过会报错。undolog段称为undolog或undoslot或undo;一个undolog对象对应多个undolog记录,即记录的历史版本。(回滚段->多个Undolog段(undolog)->多个undolog记录)。问题2:mysql回滚段和undo段的区别感觉两者应该差不多吧?结论:Rollbacksegment和undosegment是包含的,每个Rollbacksegment有1024个undosegment。撤消日志有两个用途:它提供回滚和多行版本控制(MVCC)。当数据被修改时,不仅会记录redo,还会记录相应的undo。如果事务失败或者由于某种原因回滚,可以使用undo来回滚。undolog和redolog记录的物理日志不一样,是逻辑日志。可以认为,当一条记录被删除时,undolog中会记录一条对应的insert记录,反之,当一条记录被更新时,会记录一条对应的update记录。回滚段称为一个回滚段,每个回滚段中有1024个undolog段。重做日志文件(RedoLog)重做日志就是重做日志,字面意思就是可以重做的事情。事实上,它确实是这个意思。对于上面的update语句updatetsetc=c+1whereid=2,我们正常的实现思路应该是:找到id为2的记录,取出其c字段的值。c字段的值加一,然后更新id为2的字段的c字段。但实际上MySQL并没有这样做,因为上面的实现方式虽然可以实现,但是需要读取磁盘每次查找记录并写入磁盘以更新记录。整个过程的磁盘IO成本非常高。为了提高效率,MySQL使用了一种叫做WAL(Write-AheadLogging)的技术,在写入之前记录变更日志(redolog),等待合适的时间再将变更应用到数据库中。因为我们记录了操作,所以我们可以重现这个操作,就好像我们重现了东西一样,所以叫redolog。使用WAL技术,上述update语句的大致实现思路变为:记录update操作日志:需要将id为2的记录的c字段加1,在某个时刻,MySQL数据库应用这个redolog为数据库id为2的记录的c字段加1。注意:redolog不会应用到磁盘表空间,而是在重启时应用到内存表空间缓存,以实现crash-safe。可见使用WAL技术不需要读写磁盘,大大提高了执行效率。下面举一个非常形象的例子来理解WAL技术。试想有一家酒馆,生意很好,老板愿意赊账。每次有人要赊账,老板都要翻账本看这个人有没有信用。如果有信用,他需要把这次消费的金额加到原来的信用额上。在酒馆人不多的时候,这个方法还是可以应付的。但一旦到了酒馆的高峰时间,大家都在等着结账,此时用这种方式结账很可能会让顾客等得太久,引起公愤。于是老板想了个办法:我不去账本上问谁贷了,我直接在黑板上记谁贷了多少。例如:张三记入3银元,李四记入4银元。生意不忙的时候,老板拿出账本,把粉板上的变化记入账本:哦,张三之前入账了4个银元,现在入账了3个银元,所以张San现在总共有7个银元银元的信用。在这个例子中,账本相当于我们的MySQL数据库,粉板相当于我们的redolog,保存的是消费记录。独占表空间(File-Per-TableTablespaces)如果我们将innodb_file_per_table设置为on,那么每个表都会独立生成一个表空间文件,以ibd结尾,表的数据、索引和内部数据字典信息会保存在在这个单独的表空间文件中。但是有些信息,比如undolog信息,还是会保存在系统表空间中。比如我们在test数据库中创建了一张名为user的表,那么在mysql数据文件夹的test文件夹下就会有一个名为user.ibd的文件。通用表空间(GeneralTablespaces)通用表空间是MySQL后续引入的表空间。它类似于系统表空间,可用于存储表数据和索引。它的作用是将一些业务逻辑不同的表存放在这个公共表空间中,从而达到物理隔离的效果。临时表空间(TemporaryTablespaces)存储临时表数据,包括用户创建的临时表,以及磁盘内部的临时表。对应数据目录下的ibtmp1文件。当数据服务器正常关闭时,表空间被删除并在下次重新生成。参考资料:MySQL::MySQL5.7参考手册::14.6.3TablespacesInnoDB内存结构和磁盘结构_wang2963973852的博客-CSDN博客【Mysql】漫游undo日志|图川的私密之地详析MySQL事务日志(undolog)-裸奔小鸵鸟-博客园