InnoDB存储引擎内部结构

InnoDB存储引擎内部结构

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

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

InnoDB存储引擎体系结构图

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

InnoDB存储引擎的内存结构大体以下图: InnoDB内存结构git

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

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

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

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_COUNTER 2 计数器,用来排序记录,以进入insert buffer的顺序
IBUF_REC_OFFSET_TYPE 1 操做类型(ibuf_op_t)
IBUF_REC_OFFSET_FLAGS 1 标志位,当前只有IBUF_REC_COMPACT

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

名称 大小(Bit) 说明
IBUF_BITMAP_FREE 2 该页中空闲页的数量
IBUF_BITMAP_BUFFERED 1 该页有变动缓存在change buffer
IBUF_BITMAP_IBUF 1 该页为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架构

相关阅读:

相关文章
相关标签/搜索