InnoDB存储引擎内部结构

InnoDB存储引擎内部结构

从MySQL5.6开始,InnoDB是MySQL数据库的默认存储引擎。它支持事务,支持行锁和外键,通过MVCC来获得高并发。

MySQL5.6的InnoDB存储引擎体系结构图如下:

InnoDB存储引擎体系结构图

此图引用自姜承尧的博客: http://insidemysql.blog.163.com/blog/static/202834042201311104202283/

InnoDB存储引擎的内存结构大致如下图: InnoDB内存结构

InnoDB存储引擎物理结构如下图: InnoDB表空间与段区页的关系

InnoDB的源码地址:https://github.com/mysql/mysql-server/tree/mysql-5.6.34/storage/innobase

接下来逐个介绍InnoDB存储引擎架构的各个部分。

InnoDB Buffer Pool(InnoDB缓冲池)

InnoDB缓冲池是内存中用来缓存表数据和索引的一片区域。

当缓冲池大小达到GB级别时,通过设置多个缓冲池实例,可以提高并发处理能力,减少数据库内部资源竞争。对于存储到或从缓冲池中读取的每个页,都使用哈希函数随机分配到不同的缓冲池实例中。

  • innodb_buffer_pool_size: 设置缓冲池的大小(单位: Byte),重启生效。
  • innodb_buffer_pool_instances: 设置缓冲池实例的个数,每个缓冲池管理自己的free lists,flush lists,LRU list和其他数据结构,并受到自己的互斥锁保护。此值默认为1,最大为64。只有当 innodb_buffer_pool_size 超过1G时,此参数才会起作用。重启生效。
#查看buffer pool的大小(单位: Byte)
mysql> show variables like 'innodb_buffer_pool_size';

+-------------------------+-------------+
| Variable_name           | Value       |
+-------------------------+-------------+
| innodb_buffer_pool_size | 85899345920 |
+-------------------------+-------------+
1 row in set (0.01 sec)

#查看buffer pool实例个数
mysql> show variables like 'innodb_buffer_pool_instances';
+------------------------------+-------+
| Variable_name                | Value |
+------------------------------+-------+
| innodb_buffer_pool_instances | 4     |
+------------------------------+-------+
1 row in set (0.01 sec)

缓冲池的LRU算法

InnoDB以列表的方式管理缓冲池,使用优化后的LRU算法。此算法可以最大限度地减少进入缓冲池并从未被再次访问的页的数量,这样可以确保热点页保持在缓冲池中。

当缓冲池的free list没有可用的空闲页时,InnoDB会回收LRU列表中最近最少使用的页,并将新读取到的页添加到LRU列表的midpoint位置,称之为“midpoint insertion strategy”。它将LRU列表视为两个子列表,midpoint之前的列表称为new子列表,包含最近经常访问的页;midpoint之后的列表称为old子列表,包含最近不常访问的页。

最初,新添加到缓冲池的页位于old子列表的头部。当在缓冲池中第一次访问这些页时,会将它们移到new子列表的头部,此时发生的操作称为page made young。随着数据库的运行,缓冲池中没有被访问到的页由于移到LRU列表的尾部而变老,最终会回收LRU列表尾部长时间未被访问的页。

可以通过 innodb_old_blocks_pct 参数设置old子列表在LRU列表中所占的比例。默认值为37,对应3/8的位置。取值范围从5到95。

为什么要将新读取到的页放在midpoint位置而不是LRU列表的头部?若直接将读取到的页插入到LRU列表的头部,当出现全表扫描或索引扫描的时候,需要将大量的新页读入到缓冲池中,导致热点页从缓冲池刷出,而这些新页可能仅在这次查询中用到,并不是热点数据,这样就会额外产生大量的磁盘I/O操作,影响效率。为了避免此问题,InnoDB引擎引入了参数:innodb_old_blocks_time,此参数表示第一次读取old子列表中的页后,需要等待多少毫秒才会将此页移到new子列表。默认值为1000。增加此值可以让更多的页更快的老化。

show engine innodb status 命令输出信息的BUFFER POOL AND MEMORY部分可以看到LRU算法的运行情况。详情查看:Monitoring the Buffer Pool Using the InnoDB Standard Monitor

Change Buffer(Insert Buffer的升级)

change buffer用来缓存不在缓冲池中的辅助索引页(非唯一索引)的变更。这些缓存的的变更,可能由INSERT、UPDATE或DELETE操作产生,当读操作将这些变更的页从磁盘载入缓冲池时,InnoDB引擎会将change buffer中缓存的变更跟载入的辅助索引页合并。

不像聚簇索引,辅助索引通常不是唯一的,并且辅助索引的插入顺序是相对随机的。若不用change buffer,那么每有一个页产生变更,都要进行I/O操作来合并变更。使用change buffer可以先将辅助索引页的变更缓存起来,当这些变更的页被其他操作载入缓冲池时再执行merge操作,这样可以减少大量的随机I/O。change buffer可能缓存了一个页内的多条记录的变更,这样可以将多次I/O操作减少至一次。

在内存中,change buffer占据缓冲池的一部分。在磁盘上,change buffer是系统表空间的一部分,以便数据库重启后缓存的索引变更可以继续被缓存。

innodb_change_buffering 参数可以配置将哪些操作缓存在change buffer中。可以通过此参数开启或禁用insert操作,delete操作(当索引记录初始标记为删除时)和purge操作(当索引记录被物理删除时)。update操作是inset和delete操作的组合。该参数的取值如下:

  • all: 默认值,包含inserts、deletes和purges
  • none: 不缓存任何操作
  • inserts: 缓存insert操作
  • deletes: 缓存标记删除(delete-marking)操作
  • changes: 缓存inserts和deletes
  • purges: 缓存后台进程发生的物理删除操作

对应源码如下: https://github.com/mysql/mysql-server/blob/mysql-5.6.34/storage/innobase/include/ibuf0ibuf.h

/* Possible operations buffered in the insert/whatever buffer(insert buffer中可能缓存的操作类型). See
ibuf_insert(). DO NOT CHANGE THE VALUES OF THESE, THEY ARE STORED ON DISK. */
typedef enum {
	IBUF_OP_INSERT = 0,
	IBUF_OP_DELETE_MARK = 1,
	IBUF_OP_DELETE = 2,

	/* Number of different operation types.(操作类型的数量) */
	IBUF_OP_COUNT = 3
} ibuf_op_t;

/** change buffer可以缓存的操作的组合 */
typedef enum {
	IBUF_USE_NONE = 0,
	IBUF_USE_INSERT,	/* insert */
	IBUF_USE_DELETE_MARK,	/* delete */
	IBUF_USE_INSERT_DELETE_MARK,	/* insert+delete */
	IBUF_USE_DELETE,	/* delete+purge */
	IBUF_USE_ALL,		/* insert+delete+purge */

	IBUF_USE_COUNT		/* number of entries in ibuf_use_t */
} ibuf_use_t;

change buffer的内部实现及merge操作

Change buffer的数据结构是一颗B+树。在MySQL4.1之前,每张表有一个insert buffer tree。自从MySQL4.1开始,全局只有一个inset buffer tree,这个B+树在系统表空间中。数据存储在这颗B+数的叶子节点,非叶子节点存放查询的search key:(space_id, marker, page_no)。

每个字段的含义:

  • space_id 表示修改记录所属表的表空间id,占用4字节。
  • marker 占用1字节,用来兼容MySQL4.1之前的insert buffer。
  • page_no 表示页的偏移量,占用4字节。

当一条辅助索引的记录变更要插入到页时,若这个也不在缓冲池中,那么InnoDB引擎首先根据上述规则构造一个search key,然后查找在insert buffer树,将这条记录的变更插入到insert buffer树的叶子节点。插入的记录有如下部分组成:

-------------------------------------------------------------------
| space id | marker | page no | metadata |    |    |    |    |    |
-------------------------------------------------------------------
                                         |<---- 辅助索引记录 ------>|

第4个字段metadata占用4个字节,存储的内容如下:

名称字节说明
IBUF_REC_OFFSET_COUNTER2计数器,用来排序记录,以进入insert buffer的顺序
IBUF_REC_OFFSET_TYPE1操作类型(ibuf_op_t)
IBUF_REC_OFFSET_FLAGS1标志位,当前只有IBUF_REC_COMPACT

为了保证change buffer的merge操作必须成功,需要有一个特殊的页用来标记每个辅助索引页的可用空间,这个页的类型为Insert buffer bitmap(简称ibuf bitmap)。每个辅助索引页在ibuf bitmap页中占用4位,由三部分组成,如下:

名称大小(Bit)说明
IBUF_BITMAP_FREE2该页中空闲页的数量
IBUF_BITMAP_BUFFERED1该页有变更缓存在change buffer
IBUF_BITMAP_IBUF1该页为change buffer的索引页

当ibuf bitmap页中记录的辅助索引页的可用空间不足时,会执行merge操作。

change buffer的merge操作在一下情况下发生:

  • 辅助索引页被载入到缓冲池
  • Insert Buffer Bitmap页追踪到该辅助索引页已无可用空间
  • Master Thread中会每1秒或每10秒执行一次merge操作

从MySQL5.6.2开始,可以通过 innodb_change_buffer_max_size 参数来控制change buffer最大占用缓冲池总大小的百分比。默认值为25,最大值为50。

可以通过 SHOW ENGINE INNODB STATUS 命令来查看change buffer的状态信息:

-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 0, seg size 2, 0 merges
merged operations:
 insert 0, delete mark 0, delete 0
discarded operations:
 insert 0, delete mark 0, delete 0
Hash table size 4425293, used cells 32, node heap has 1 buffer(s)
13577.57 hash searches/s, 202.47 non-hash searches/s

Adaptive Hash Index(AHI,自适应哈希索引)

自适应哈希索引是InnoDB表通过在内存中构造一个哈希索引来加速查询的优化技术,此优化只针对使用 '=' 和 'IN' 运算符的查询。MySQL会监视InnoDB表的索引查找,若能通过构造哈希索引来提高效率,那么InnoDB会自动为经常访问的辅助索引页建立哈希索引。

这个哈希索引总是基于辅助索引(B+树结构)来构造。MySQL通过索引键的任意长度的前缀和索引的访问模式来构造哈希索引。InnoDB只为某些热点页构建哈希索引。

思考:为什么只是基于辅助索引页来构造哈希索引?

可通过 innodb_adaptive_hash_index 参数开启或禁用此功能,默认是开启状态。开启此功能后, InnoDB会根据需要自动创建这个哈希索引,而不用人为干预创建,这就是叫自适应的原因。此功能并不是在所有情况下都适用,且AHI需要的内存都是从缓冲池申请的,所以此功能的开启或关闭需要通过测试来具体确定。可以通过 SHOW ENGINE INNODB STATUS 命令查看AHI的使用状况。

Redo Log

重做日志用来实现事务的持久性。其由两部分组成:一是内存中的重做日志缓冲(redo log buffer),其是易失的;二是磁盘上的重做日志文件(redo log file),其是持久的。

在MySQL数据库宕机恢复期间会用到重做日志文件,用于更正不完整事务写入的数据。

InnoDB通过Force Log at Commit机制实现事务的持久性,即在事务提交前,先将事务的重做日志刷新到到重做日志文件。

重做日志在磁盘上由一组文件组成,通常命名为 ib_logfile0 和 ib_logfile1。

MySQL以循环方式将日志写入重做日志文件。假设现在有两个重做日志文件:ib_logfile1和ib_logfile1。重做日志先写入到ib_logfile1,当ib_logfile0写满后再写入ib_logfile1。当ib_logfile1也写满后,再往ib_logfile0中写,而之前的内容会被覆盖。

innodb_log_file_size 参数用来设置每个重做日志文件的大小(单位:Byte)。innodb_log_files_in_group 参数用来设置重做日志文件组中日志文件的个数。 innodb_log_group_home_dir 参数设置重做日志文件所在的路径。从MySQL5.6.3开始,重做日志文件总大小的最大值从之前的4GB提升到了512GB。

mysql> show variables like 'innodb_log_%';
+-------------------------------+-----------------------+
| Variable_name                 | Value                 |
+-------------------------------+-----------------------+
| innodb_log_file_size          | 1073741824            |
| innodb_log_files_in_group     | 3                     |
| innodb_log_group_home_dir     | /opt/local/mysql/var/ |
...
+-------------------------------+-----------------------+
10 rows in set (0.01 sec)

重做日志(redo log)跟二进制日志(binlog)的区别:

  • 重做日志在InnoDB存储引擎层产生;而二进制日志在MySQL数据库上层产生,不仅仅针对InnoDB引擎,MySQL中的任何存储引擎对于数据库的变更都会产生二进制日志。
  • 两者记录的内容形式不同,二进制日志是一种逻辑日志,其记录的是对应的SQL语句;重做日志是物理格式日志,其记录的是每个页的变更。
  • 两种日志记录写入磁盘的时间点不同,二进制日志只在事务提交完成后进行写入;而重做日志在事务进行中被不断的写入,也就是日志并不是随事务提交的顺序写入的。

Redo Log Buffer(重做日志缓冲)

重做日志缓冲是一块内存区域,用来缓存即将被写入到重做日志文件的数据。InnoDB引擎首先将重做日志信息缓存到重做日志缓冲,然后定期将其刷新到磁盘上的重做日志文件。

如下三种情况会将重做日志缓冲中的数据刷新到磁盘的重做日志文件中:

  • Master Thread会定期将重做日志缓冲刷新到重做日志文件,即使这个事务还没有提交。
  • 事务提交时
  • 当重做日志缓冲没有足够的空间时

InnoDB通过Force Log at Commit机制实现事务的持久性,即在事务提交前,先将事务的重做日志刷新到到重做日志文件。

innodb_flush_method 定义用于将数据刷新到InnoDB数据文件和日志文件的方法,会影响I/O吞吐量。默认值为NULL,可选项包含:fsync、O_DIRECT和其他。若在Unix-like系统上此参数设置为NULL,那么默认使用fsync。

fsync: InnoDB调用系统的fsync()刷新数据文件和日志文件。

O_DIRECT: InnoDB使用O_DIRECT方式打开数据文件,然后使用fsync()刷新数据文件和日志文件。启用后将绕过操作系统缓存,直接写文件。了解更多可以查看: InnoDB O_DIRECT选项漫谈(一)

为确保每次重做日志缓冲都能写入到磁盘的重做日志文件,在每次将重做日志缓冲写入重做日志文件后,InnoDB引擎都需要调用一次fsync操作。由于默认情况下 innodb_flush_method 参数未设置为O_DIRECT,因此重做日志缓冲先写入文件系统缓存。

innodb_log_buffer_size 参数可以设置重做日志缓冲的大小。

innodb_flush_log_at_trx_commit 参数用来控制重做日志缓冲刷新到磁盘的策略。取值范围如下:

  • 1: 默认值。表示事务提交时必须调用一次fsync操作。
  • 0: 表示事务提交时不进行刷新日志到磁盘。这个操作在master thread中完成,在master thread中每1秒会进行一次刷新日志到磁盘操作。
  • 2: 表示事务提交时将重做日志缓冲写入重做日志文件,但仅写入文件系统的缓存中,不进行fsync操作。此情况下,若操作系统发生宕机,会丢失未从文件系统缓冲刷新到重做日志文件的那部分日志。

Undo Log

undo log(也称为rollback segment)用来存储被事务修改的记录的副本。

undo日志有两个作用:一个是实现事务的原子性,即当事务由于意外情况未能成功运行时,可以使事务回滚,从而让数据恢复到事务开始时的状态;另一个作用是实现MVCC机制,当用户读取一行记录时,若该记录已经被其他事务占有,当前事务可以通过undo日志读取该记录之前的版本信息,以此实现一致性非锁定读。

每个回滚段(rollback segment)记录了1024个undo段(undo segment),InnoDB引擎在每个undo段中进行undo页的申请。

undo log分为insert undo log和update undo log。

  • insert undo log指事务在INSERT操作中产生的undo日志。因为INSERT操作的记录仅对当前事务可见,所以该undo日志在事务提交后可以直接删除。
  • update undo log通常保存的是对DELETE和UPDATE操作产生的undo日志。该undo日志可能需要提供MVCC机制,因此其不能在事务提交后立即删除。当事务提交后,它会放入回滚段的history链表的头部,等待purge线程进行最后的删除。

默认情况下,undo日志位于系统表空间(system tablespace)中。从MySQL5.6起,可以通过 innodb_undo_tablespaces 和 innodb_undo_directory 参数将undo日志存放在独立表空间中。详情查看: Storing InnoDB Undo Logs in Separate Tablespaces

  • innodb_undo_directory: 用于设置undo log文件所在的路径。
  • innodb_undo_logs: 用于设置undo log的个数,默认值为128。
  • innodb_undo_tablespaces: 当 innodb_undo_logs 设置为非0值时,此参数用于设置undo log分割的表空间文件的数量,文件名字为undoN。

System Tablespace(系统表空间)

InnoDB系统表空间包含InnoDB数据字典(InnoDB相关对象的元数据)、双写缓冲(doublewrite buffer)、change buffer和undo logs。此外,还包含用户在系统表空间中创建的表数据和索引数据。由于多个表的数据可以在共同存放在系统表空间中,以此其也称为共享表空间。

系统表空间可由一个或多个文件组成。默认情况下,在MySQL数据目录中有一个命名为 ibdata1 的系统表空间文件。innodb_data_file_path 参数可以设置系统表空间文件的大小和数量。具体如何设置此参数可查看: System Tablespace Data File Configuration

可以通过 innodb_file_per_table 参数启用独立表空间。即每创建一个表就会产生一个单独的 .ibd 文件存放此表的记录和索引。若未启用此参数,那么InnoDB引擎创建的表就会存在于系统表空间中。

Doublewrite Buffer(双写缓冲)

双写缓冲技术是为了解决partial page write问题而开发的。doublewrite buffer是系统表空间上的连续的128个页(两个区),大小为2M。

当发生数据库宕机时,可能InnoDB存储引擎正在写入某个页到表中,而这个页只写了一部分,比如16KB的页,只写了前4KB,之后就发生了宕机,这种情况被称为部分写失效(partial page write)。

doublewrite的工作原理是:在将缓冲池中的页写入磁盘上对应位置之前,先将缓冲池中的页copy到内存中的doublewrite buffer,之后再分两次,每次1M,顺序地将内存中doublewrite buffer中的页写入系统表空间中的doublewrite区域,让后立即调用系统fsync函数,同步数据到磁盘文件中,避免缓冲写带来的问题。在完成doublewrite页的写入之后,再将内存上doublewrite buffer中的页写入到自己的表空间文件。

InnoDB存储引擎中doublewrite的体系架构如下图: InnoDB存储引擎的doublewrite架构

相关阅读:

转载于:https://my.oschina.net/u/553773/blog/792144

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值