likes
comments
collection
share

简单了解InnoDB存储引擎的内部架构

作者站长头像
站长
· 阅读数 29

MySQL支持的存储引擎有多种,如InnoDB,MyISAM,CSV,Memory,BlackHole等。但是在一般的业务场景下,通常使用较多的是InnoDB。InnoDB擅长处理事务并且具有自动崩溃恢复的特性,除此之外,InnoDB支持行锁,可以支持更高的并发性能。那么InnoDB到底是怎么工作的呢,下面就来简单的了解一下。

InnoDB的架构体系

在MySQL5.7中,官网给出的架构图分成两个部分:一部分是在内存中的结构,另一部分是磁盘中的结构。 简单了解InnoDB存储引擎的内部架构

InnoDB内存结构

从上面的架构图中可以知道InnoDB在内存中的结构有四种,分别为:Buffer Pool,Change Buffer,Log Buffer以及Adaptive Hash Index。

Buffer Pool

Buffer Pool(BP)由一个个的数据页构成,每个页的大小大概在16kb。BP中存储着索引数据和一些缓存数据,如果有经常访问的数据,那么会优先从BP中获取,从而提高了系统的查询效率。数据页根据状态可以分为三种类型,分别为:

  • free page: 空闲数据页,未被使用
  • clean page: 数据页被使用,但内部数据未被修改
  • dirty page: 脏数据页,数据页被使用且内部数据被修改

为了提高数据页的管理效率,BP根据数据页不同的类型用不同的链表来进行管理和维护:

  • Free list: 表示空闲缓冲区链表,为了能够快速找到空闲的数据页,将BP中的free page数据页维护到这个链表中。当链表中的数据页被使用时,该数据页节点会被删除掉。

简单了解InnoDB存储引擎的内部架构

  • Flush list: 表示需要刷新到磁盘的缓冲区,由于数据被修改造成缓存与磁盘数据不一致,适用于dirty page。为了避免频繁的将修改的数据刷到磁盘,BP将待刷盘的脏页维护到Flush list中,再由后台线程将其刷到磁盘上。其数据结构与Free list类似。

简单了解InnoDB存储引擎的内部架构

  • LRU list: 表示正在使用中的缓冲区,适用于clean page和dirty page。这里的LRU和传统的LRU算法整体上一样,但是有所不同的是BP的LRU List整体上分成了三个部分,分别为New Sublist , Old Sublist和Midpoint。New Sublist存储最近被使用的数据页,Old Sublist存储最近最久未被使用的数据页。当访问Old Sublist中的数据页时,该数据页会被重新加载到New Sublist。当需要插入新的数据页元素时,并不是立刻插入到New Sublist中,而是插入到Midpoint中节点处,也就是Old Sublist的头部。如果数据页在LRU list中长时间未被使用并且没有空闲的数据页时,那么将会淘汰这个数据页。

简单了解InnoDB存储引擎的内部架构

InnoDB为什么不使用普通的LRU,而是使用改进的LRU算法呢?

普通的LRU当访问当列表中的元素时,该元素会被添加到列表的头部。当有新元素需要添加到队列时,那么就会移除掉列表最后的元素。比如说现在有1,2,3,4,5这五个元素,当访问3这个元素时,那么这个元素就会被排到首位。

简单了解InnoDB存储引擎的内部架构 这个时候再次访问一个列表中不存在的元素6,按照最近最久未使用的原则,5这个元素将会被淘汰,而6将会被排到首位。

简单了解InnoDB存储引擎的内部架构 那么现在问题来了,普通的LRU不是也可以满足InnoDB的需求吗,可以保证淘汰策略啊,那为什么不使用呢?这是因为空间局部性的原因。如果使用普通的LRU算法,当访问某个元素时,那么由于空间局部性的原因,InnoDB会认为该元素附近的其他元素也会被访问到。所以,会把该元素以及附近的其他元素一并排到头部及靠前位置。本着有被访问的可能性,将附近元素排到的头部位置,如果这些元素没有被访问,还占着靠前的位置,那么这个预处理操作就失效了,缓存的命中率也就下降了。这种现象称为预读失效,这也是为什么不使用普通LRU的原因。改进版的LRU,在添加元素时,并不会从头部添加,而是从Old Sublist的头部作为插入点,当被再次访问时,该节点将会排到头部,即使元素不会被访问,也会很快被淘汰掉。

简单了解InnoDB存储引擎的内部架构

那New Sublist的容量和Old SubList的容量是如何分配的呢?官方给出的标准是Old Sublist占BP的3/8,New Sublist占BP的5/8。也可以通过sql来查看容量分配情况,结果中的37表示的是百分比,也就是37%,约等于3/8。

show variables like '%innodb_old%'; --查看LRU中old sublist占比大小

简单了解InnoDB存储引擎的内部架构 因为Buffer Pool是属于内存结构的,那么如何知道占用了多少内存空间呢?又如何去修改占用空间的大小呢?将空间设置为多少才最为合理呢?默认情况下,Buffer Pool的空间占用大概在8MB左右,可以通过sql来进行查看。

-- 查看Buffer Pool内存大小 --
show variables like '%innodb_buffer_pool%';
select @@innodb_buffer_pool_size/1024/1024;

简单了解InnoDB存储引擎的内部架构

简单了解InnoDB存储引擎的内部架构 除了innodb_buffer_pool_size外,还有两个重要参数innodb_buffer_pool_instances和innodb_buffer_pool_chunk_size分别表示实例数和块大小,这两个参数可以决定Buffer Pool的内存大小。

innodb_buffer_pool_size的大小必须等于innodb_buffer_pool_instances * innodb_buffer_pool_chunk_size或者是其结果的倍数。如果在配置innodb_buffer_pool_size不等于其结果或者>不是其结果的倍数,那么innodb_buffer_pool_size将会自动进行调整。

比如,将innodb_buffer_pool_size的大小修改为8588608(原来的值为8388608),这个值并不是实例数与块大小的乘积,当保存后发现innodb_buffer_pool_size值为16777216(大约16MB,为原来的两倍)。

set global innodb_buffer_pool_size = 8588608; -- 设置Buffer Pool的大小--

select @@innodb_buffer_pool_size/1024/1024;   -- 查看Buffer Pool的大小--

简单了解InnoDB存储引擎的内部架构

那么这个值到底该设置为多大才好呢,大约在系统内存的60%-80%之间最为合适。

Change Buffer

Buffer Pool对于读操作,可以提高效率,减少磁盘IO。而Change Buffer则是为了减少写操作的磁盘IO。如果在不使用Change Buffer的情况下去执行写操作时,会有两种情况:

  • 如果Buffer Pool中存在该目标数据页

如果Buffer Pool中存在,那么直接在内存中进行修改,数据页有clean page变为dirty page。最终由后台线程将数据刷到磁盘。

简单了解InnoDB存储引擎的内部架构

  • 如果Buffer Pool中不存在该目标数据页

如果Buffer Pool不存在,那么先会进行一次磁盘IO将磁盘上的数据读取到Buffer Pool的数据页中,之后再对这个数据页进行修改。

简单了解InnoDB存储引擎的内部架构

在Buffer Pool中进行写操作,如果缓存命中那只需要进行一次内存操作就可以完成,效率很高。但是如果缓存没有命中,那么就需要至少一次IO操作,在写多读少的情况下效率比较低下。Change Buffer与Buffer Pool不同处在于,当执行写操作时,如果对应的数据页不存在,那么就会寻找一个free page进行存储。如果下次对该数据进行读操作时,Buffer Pool将会读取磁盘上的数据并且与Change Buffer中的数据进行合并,最终填充到Buffer Pool中。

简单了解InnoDB存储引擎的内部架构

需要注意的是,Chang Buffer适用于非唯一的普通索引,如果设置了唯一索引,那么在写操作时,InnoDB需要校验唯一性,这个时候不得不进行IO操作,Change Buffer存在的意义就不大了。还有一点,Chang Buffer是占用Buffer Pool的内部空间,最大占比大约为25%左右,可以使用sql进行查看。如果业务场景是写多读少,那么可以适当调整Change Buffer的占比。

select @@innodb_change_buffer_max_size; --查看innodb中Change Buffer占用大小

set global innodb_change_buffer_max_size = 30; --修改innodb中Change Buffer占用大小

Log Buffer

Log Buffer是记录InnoDB运行时产生日志(Redo或Undo)缓冲区,Log Buffer中的数据会定期刷新到磁盘中的日志文件中,当容量达到最大值时会也会自动刷新到磁盘中。如果有大量的事务更新操作,可以适当增加Log Buffer的容量,这样可以减少磁盘IO操作。

InnoDB的磁盘结构

InnoDB在磁盘上的结构主要分为两个部分,分别TableSpaces表空间和Log日志。其中表空间分为System Tablespace,Undo Tablespace , General Tablespace , Temporary Tablespace以及File-Per_table Tablespace。日志分为Redo Log和Undo Log。

Tablespace

  • System Tablespace系统表空间

系统表空间中存储区包括InnoDB存储引擎的数据字段,双写缓冲区(doublewrite buffer),更改缓冲区(change buffer),撤销日志(undo logs)。如果表是在系统表空间被创建,那么还有可能包括表和表的索引数据。系统表空间是一个共享表空间,可以被多张表共享。

  1. Data Dictionary数据字典

InnoDB数据字典由内部系统表组成,其中包含用于跟踪表、索引和表列等对象的元数据 。元数据物理上位于InnoDB系统表空间中。由于历史原因,数据字典元数据在某种程度上与存储在InnoDB表元数据文件(.frm文件)中的信息重叠。

  1. Doublewrite Buffer双写缓冲区

双写缓冲区是处于Buffer Pool将脏页写入到磁盘过程中中间的位置,也就是说Buffer Pool中的脏页并不是直接刷到磁盘上的,而是将脏页数据刷新到双写缓冲区中,再由双写缓冲区进行落盘。虽然数据被写入了两次,但是双写缓冲区不需要过多的IO开销。如果在脏页写入过程中出现操作系统、存储子系统或mysqld进程崩溃,那么在恢复时可以从双写缓冲区中找到未落盘的数据,减少数据丢失的风险,从而达到备份的效果。在8.0版本中,Doublewrite Buffer从系统表空间中剥离了出来。

  • Undo Tablespace撤销表空间

撤销表空间有多个Undo Log组成。在MySQL 5.7版本之前Undo占用的是System Tablespace共享区,从5.7开始将Undo从System Tablespace分离了出来。撤销表空间的数量由innodb_undo_tablespaces配置选项控制,默认为0。需要注意的是,官方已经将innodb_undo_tablespaces弃用,在以后的高版本中可能会被删除。

  • General Tablespace通用表空间

使用CREATE TABLESPACE语法创建的共享InnoDB表空间。通用表空间可以在MySQL数据目录之外创建,能够保存多个表,并支持所有行格式的表。

  • Temporary Tablespace临时表空间

临时表空间分session temporary tablespaces(8.0版本)和global temporary tablespace两种类型。 session temporary tablespaces存储的是用户创建的临时表或磁盘内部的临时表。global temporary tablespace储存用户临时表的回滚段。临时表空间在数据库服务正常启动或者异常导致的服务终止时会被清除掉。

  • File-Per-table Tablespace独立表空间

独立表空间中包含单个表的数据以及索引(存储在.idb文件中)。从5.6版本后,innodb默认开启innodb_file_per_table选项,开启该选项后innodb会在独立表空间中创建表。如果关闭该选项,那么在创建表时,会导致在系统表空间中创建表。独立表空间相比较系统表空间,独立表空间在删除或截断表后,磁盘空间会进行自动释放,而系统表空间在删除表后,对应的磁盘空间并不会被释放。

select @@innodb_file_per_table; -- 查看独立表空间开启状态--
set GLOBAL innodb_file_per_table=ON/OFF; -- 开启或关闭独立表空间 --

Log日志

undo log

undo log是mysql中重要的持久化日志文件,其主要应用在事务场景。在开启事务前,undo log会记录事务前的数据,如果在事务过程中出现异常会系统崩溃情况,那么undo log可以恢复之前的数据并且保证其他事务能够正常读取数据。undo log产生于事务开启前,当事务提交后并不会立刻删除日志文件,因为在MVCC中可能会使用到log数据,innodb会将该事务对应的undo log记录到待删除的列表中,等待后台线程来进行删除。比如,有一张用户表user,user表中有用户id,用户名以及用户性别三个字段。在开启事务的情况下:

-- 1. 将用户id为1的性别改为女 --
update user set sex='女' where user_id = 1;

-- 2. 将用户id为1的姓名改为张三三 --
update user set name='张三三' where user_id = 1;

-- 3. 其他事务修改操作--
.....

当第一次执行update操作时,先会将原始数据同步到undo log中,除原始数据外还包含了事务id,行id以及回滚指针。当第二次执行update操作时,同样会将上一步提交后的数据同步到undo log中,此时undo log中的回滚指针指向上一次的undo log数据,从而形成了一个日志链表。

简单了解InnoDB存储引擎的内部架构

undo log的作用

  1. 实现事务原子性操作

事务处理过程中,如果出现异常或执行Rollback语句,InnoDB可以利用Undo Log日志链中的数据恢复到事务开始前的状态。

  1. 实现多版本并发控制(MVCC)

InnoDB的事务具有ACID特性,其中隔离性(RC,RR)就是通过MVCC来实现的,而MVCC又依赖与undo log。比如有两个事务A和B,A事务执行更新操作,前会将元数据备份到undo buffer中,在由undo buffer持久化到磁盘中的undo log文件中。如果在事务A执行的过程中(事务A未提交),事务B对数据进行了select操作,那么此时事务B会直接从undo buffer中查询备份数据。这样A,B两个事务就不会产生关联影响,做到了事务隔离,具体的MVCC后面再做详细介绍。

简单了解InnoDB存储引擎的内部架构

undo log的存储

undo log默认情况下是存储在共享表空间中,如果开启了独立表空间,那么undo log将会存储在.idb文件中。InnoDB对undo log的存储是采用rollback segment回滚段的方式,一个回滚段包含1024个undo log segment撤销日志段。默认情况下回滚段的数量为128个,那么就有128 * 1024个撤销日志段,也可以理解为InnoDB最多同时支持开启128*1024个事务。


select @@innodb_undo_logs; -- 查看回滚段数量

redo log

与undo log不同,redo log是在事务提交后生成的日志文件,如果服务宕机或数据丢失,通过redo日志也可以恢复数据,保证事务的持久性。当执行一条update事务时,首先会更新Buffer Pool中的数据块,此时会生成一条redo log并记录到redo log buffer中,当事务提交后,会将redo log buffer中的数据刷新到磁盘中的redo log中,在磁盘中redo数据是采用追加写的方式存储。

简单了解InnoDB存储引擎的内部架构

redo log刷盘机制

redo log的写入并不是直接写入磁盘的,InnoDB引擎会在写redo log的时候先写redo log buffer,之后以一定的频率刷入到真正的redo log file中。InnoDB提供了三种刷盘策略,可以通过sql命令进行查看

 select @@innodb_flush_log_at_trx_commit;

默认值为1,除此之外还有两个值分别为0,2。不同的值表示不同的策略。

0︰表示每次事务提交时不进行刷盘操作。(系统默认master thread每隔1s进行一次重做日志的同步)。master thread中每1秒进行一次重做日志的fsync操作,因此实例crash最多丢失1秒钟内的事务。

1:表示每次事务提交时都将进行同步,刷盘操作(默认值)。只要事务提交成功,redo log记录就一定在硬盘里,不会有任何数据丢失。如果事务执行期间MysQL挂了或宕机,这部分日志丢了,但是事务并没有提交,所以日志丢了也不会有损失。效率不高。

2︰表示每次事务提交时都只把redo log buffer内容写入page cache,不进行同步。由os自己决定什么时候同步到磁盘。只要事务提交成功,redo log buffer中的内容只写入文件系统缓存( page cache )。如果仅仅只是MySQL挂了不会有任何数据丢失,但是操作系统宕机可能会有1秒数据的丢失。效率最高。

简单了解InnoDB存储引擎的内部架构

binlog

binlog是数据MySQL级别的二进制日志文件,并不属于InnoDB,在这里也进行一个简单的介绍避免与redo log和undo log混淆。binlog基于event事件生成,记录着表结构的变更以及表数据的修改等。binlog的作用有两种,一种是用在主从复制模式中主库像从库同步数据,另一种就是数据恢复。所以,在删库后除了跑路是一个选择,binlog也是一种选择。

简单了解InnoDB存储引擎的内部架构

简单模拟通过binlog恢复数据,MySQL默认情况下关闭了binlog,如果需要,可以自行开启或关闭。

show global variables like "%log_bin%";-- 查看binlog开启状态 --

简单了解InnoDB存储引擎的内部架构 如果没有开启,可以在MySQL安装目录下配置my.ini或my.conf中进行配置,配置完成后需要重启MySQL服务。

log-bin=mysql-bin #binlog文件名前缀
binlog-format=row # Statement/Row/Mixed

当开启后,先插入一条数据,在执行一条update语句时=,最终将数据删除,此时会生成对应的binlog文件。那么这个时候,想要把数据恢复到初始状态,也就是性别是男的数据。

 insert into user(id,name,sex) values(1,'张三','男'); --插入一条数据
 update user set sex='女' where id= 1; -- 更新数据 --
 delete from user where id = 1; -- 删除数据 --

步骤一: 首先通过命令查询出正在工作的binlog文件

  show master logs;

简单了解InnoDB存储引擎的内部架构

步骤二: 通过mysqlbinlog,将事件同步到本地文件中,便于定位需要恢复的位置。mysqlbinlog需要进入到mysql安装目录中的bin目录下执行。

mysqlbinlog "xxxxpath\mysql-bin.000001">test.sql

通过同步出的文件可以根据时间定位到事件的起始以及结束位置,因为需要恢复的数据,是创建表后的首条数据,所以比较容易确定位置是在570 - 674。

简单了解InnoDB存储引擎的内部架构

步骤三: 通过mysqlbinlog,将起始位置的事件同步到本地并恢复数据。

mysqlbinlog "xxxxpath\mysql-bin.000001" --start-position=570  --stop-position=674 >test2.sql

source xxxxpath\test2.sql

执行完成后,再次查询,发现数据已被恢复。

简单了解InnoDB存储引擎的内部架构

简单了解InnoDB存储引擎的内部架构

InnoDB的线程模型(后台线程)

InnoDB中有多个线程,包括Master Thread,IO Thread,Purge Thread以及Page Cleaner Thread,每个线程的职责也是不一样的。

  • Master Thread

Master Thread是Innodb的主线程,负责调度其他线程完成合并change buffer、undo页的回收、脏页的刷新等,保证数据一致性。

  • IO Thread

为了提高InnoDB的读写性能,内部采用了AIO的方式进行读写处理。IO Thread下面继续分为read thread , write thread , insert buffer thread以及 log thread四种线程,其中read/write thread各有四个线程。

  1. read thread: 负责读取磁盘数据到Buffer Pool中的数据页。
  2. write thread: 负责将Buffer Pool中的脏页数据刷到磁盘。
  3. insert buffer thread: 负责将Change Buffer中的数据也刷新到磁盘。
  4. log thread: 负责将Log Buffer中的日志数据刷新到磁盘。
show engine innodb status; -- 查看io线程数 --

简单了解InnoDB存储引擎的内部架构

  • Purge Thread

Purge Thread负责undo log的清理工作。当事务提交后,一些undo log就没有存在的必要了,那么这个时候后台线程Purge就会进行清理。

  • Page Cleaner Thread

Page Cleaner Thread负责将Buffer Pool中的脏页,刷新到磁盘上。在IO Thread中同样有write thread负责将脏页写到磁盘,那为什么还要有Page Cleaner Thread呢,其实是在Page Cleaner Thread内部会调用write thread。

总结

上面几个部分分别对InnoDB的内存结构以及磁盘结构做了简单的介绍,阐述了不同的结构发挥了不同的作用,其最终目的都是为了提高数据的操作效率以及数据的安全性和一致性。比如像Buffer Pool减少读取操作的磁盘IO,Change Buffer减少写操作的磁盘IO,redo log可以保证事务操作的持久性,undo log可以保证事务操作的隔离性等等。上述的一些知识点中肯定会有一些不完整或不准确的地方,后期会在不断的进行修改和补充。

**参考资料 **

  1. MySQL 5.7 Reference Manual

  2. 《MySQL技术内幕 InnoDB存储引擎》

转载自:https://juejin.cn/post/7142891691526062094
评论
请登录