浅析 InnoDB 存储引擎的工作流程

InnoDB

InnoDB 是由 Innobase Oy 公司开发,该存储引擎是第一个完整支持 ACID 事务的 MySQL 存储引擎。具有插入缓存两次写自适应哈希索引等关键特性,是一个高性能、高可用的存储引擎。

整体架构

InnoDB 有多个内存块,这些内存块组合在一起组成了一个大的内存池。而 InnoDB 的内存池中会有多个后台线程,这些后台线程负责刷新内存池中的数据,和将脏页(已修改的数据页)刷新到磁盘文件。

后台线程

默认情况下,InnoDB 存储引擎有 13 个后台线程:

  • 一个 master 线程
  • 一个锁监控线程
  • 一个错误监控线程
  • 十个 IO 线程
    • 插入缓存线程
    • 日志线程
    • 读线程(默认 4 个)
    • 写线程(默认 4 个)

下面是我本机上的十个 IO 线程

--------
FILE I/O
--------
I/O thread 0 state: waiting for i/o request (insert buffer thread)
I/O thread 1 state: waiting for i/o request (log thread)
I/O thread 2 state: waiting for i/o request (read thread)
I/O thread 3 state: waiting for i/o request (read thread)
I/O thread 4 state: waiting for i/o request (read thread)
I/O thread 5 state: waiting for i/o request (read thread)
I/O thread 6 state: waiting for i/o request (write thread)
I/O thread 7 state: waiting for i/o request (write thread)
I/O thread 8 state: waiting for i/o request (write thread)
I/O thread 9 state: waiting for i/o request (write thread)
Pending normal aio reads: 0 [0, 0, 0, 0] , aio writes: 0 [0, 0, 0, 0] ,
 ibuf aio reads: 0, log i/o's: 0, sync i/o's: 0
Pending flushes (fsync) log: 0; buffer pool: 0
540 OS file reads, 89 OS file writes, 7 OS fsyncs
0.00 reads/s, 0 avg bytes/read, 0.00 writes/s, 0.00 fsyncs/s

内存池

InnoDB 存储引擎的内存池包含:缓冲池、日志缓存池、额外内存池。这些内存的大小分别由配置文件中的参数决定。其中占比最大的是缓冲池,里面包含了数据缓存页、索引、插入缓存、自适应哈希索引、锁信息和数据字典。InnoDB 会在读取数据库数据的时候,将数据缓存到缓冲池中,而在修改数据的时候,会先把缓冲池中的数据修改掉,一旦修改过的数据页就会被标记为脏页,而脏页则会被 master 线程按照一定的频率刷新到磁盘中。日志缓存则是缓存了redo-log 信息,然后再刷新到 redo-log 文件中。额外内存池则是在对一些数据结构本身分配内存时会从额外内存池中申请内存,当该区域内存不足则会到缓冲池中申请。

内存结构

Master Thread

InnoDB 存储引擎的主要工作都在一个单独的 Master Thread 中完成,其内部由四个循环体构成:主循环( loop )、后台循环( background loop )、刷新循环( flush loop )、暂停循环( suspend loop )。具体工作流程如下图所示:

mysql_thread

主循环

主要负责将缓冲池中的日志文件刷新到磁盘中、合并插入缓存、刷新缓冲池中的脏页数据到磁盘中、删除无用的 Undo 页、产生一个 checkpoint 。在主循环中会多次将脏页刷新到磁盘中,但是有一些刷新任务总会执行,有一些则根据参数来判断当前是否需要刷新。而这个参数 innodb_max_dirty_pages_pct 最大脏页比例是通过配置文件决定的,你可以根据实际情况来调整你自己的最大脏页比例,来达到最好的性能。

伪代码如下:

for (int i = 0; i<10; i++) {
    thread_sleep(1)
    do log buffer flush to disk
    if ( last_one_second_ios < 5) {
        do merge at most 5 insert buffer
    }
    if (buf_get_modified_ratio_pct > innodb_max_ditry_pages_pct) {
        do buffer pool flush 100 dirty page
    }
    if (no user activity) {
        goto background loop
    }
}
if (last_ten_second_ios < 200) {
    do buffer pool flush 100 dirty page
}
do merge at most 5 insert buffer
do log buffer flush to disk
do full pourge
if (buf_get_modifued_ratio_pct > 70%) {
    do buffer pool flush 100 dirty page
} else {
    buffer pool flush 10 dirty page
}
do fuzzy checkpoint
goto loop

后台循环

在后台循环中 InnoDB 会做这些事:删除无用的Undo页、合并插入缓存。如果当前 InnoDB 处于空闲状态,则跳转到刷新循环,否则跳转到主循环继续处理数据。

伪代码如下:

do full purge
do merge 20 insert buffer
if (not idle) {
    goto loop
} else {
    goto flush loop
}

刷新循环

一旦执行到刷新循环,InnoDB 会一直处理脏页数据,直到脏页数据达到最大脏页比例以下。这时候会跳转到暂停循环中(所有数据都处理完毕)。

伪代码如下:

flush loop:
do buffer pool flush 100 dirty page
if (buf_get_modified_ratio_pct > innodb_max_dirty_pages_pct) {
    goto flush loop
} else {
    goto suspend loop
}

暂停循环

在本循环中,InnoDB会将 Master Thread 挂起,减少内存资源使用,一直处于 waiting 状态,等待事件来唤醒。一旦有新的事件过来,就跳转到主循环中。

伪代码如下:

suspend loop:
suspend_thread()
waiting event
goto loop;=

由此可以看出,master 线程的最大的工作内容就是刷新脏页数据到磁盘了。这一步就是把缓存池中被修改的数据页同步到磁盘中。而脏页数据的刷新基本上都是由 innodb_max_dirty_pages_pct 来控制的,所以当你的服务器处理能力比较强,给 InnoDB 分配的内存池比较大,这时候可能你的脏页数据会很难达到最大脏页比,这时候你的数据基本上都在缓冲池中,可能需要很长一段时间才会到数据库磁盘文件中,也就是脏页的刷新速度会很低(MySQL 5.1之前的版本默认是 90%,后面调整到 75%)。所以实际应用中可以根据自己内存和数据库的读写量来设置这个最大脏页比。对于一次刷新脏页数量的设置,在 InnoDB Plugin 中有一个参数 innodb_adaptive_flushing 自适应刷新,InnoDB 会根据产生的重做日志速度来计算出当前最适合的刷新脏页数量。当然 InnoDB Plugin 中还有其它很多参数配置,合理利用这些配置可以极大的提升 InnoDB 存储引擎的性能。

关键特性

前面说到 InnoDB 的三大特性分别为:插入缓存、两次写、自适应哈希索引。下面就简单介绍下这三大特性。

插入缓存

当我插入一条数据,该数据只有一个 ID 索引(聚集索引数据行的物理顺序与列值的逻辑顺序相同)的时候,并且 ID 是自增长的,这时候页中的行记录按照 ID 顺序存放,所以只需要在最新页插入数据即可。但是如果我的表有多个非聚集索引:该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同,在插入的时候非聚集索引的插入不再是顺序的,这时候要离散的访问非聚集索引页,导致插入性能变低。而插入缓存则在插入的时候判断缓冲池中是否存在当前非聚集索引,如果存在则直接插入,否则先插入到一个缓存区,然后再通过 Master Thread 来合并插入缓存。这样极大的提高了数据的写性能。

两次写

两次写是为了解决在将缓冲池中的脏页刷新到磁盘的过程中,操作系统出现故障,导致当前的脏页部分写失效的问题。通过两次写在下次恢复的时候,InnoDB 会根据两次写的结果来恢复数据。

原理:在刷新脏页的时候,不是直接把脏页数据刷新到磁盘,而是将脏页先写到一个大小为2M的内存缓存中,再将这个内存缓存数据同步到磁盘的共享表空间中。当全部都写到共享表空间后,再将数据刷新到磁盘中。这样如果发生了上面描述的情况,这时候数据会在共享表空间中有个备份,恢复的时候就可以使用共享表空间的数据。

如果有数据库集群的情况下,master数据库是一定要开启两次写的,为了保证数据可靠性。而从数据库可以通过参数 skip_innodb_doublewrite 来禁止两次写功能,来提高插入效率。

自适应哈希索引

InnoDB 会监控对表示的索引查找,如果发现可以通过对索引进行哈希来优化搜索。这时候会对当前的索引建立哈希索引。称之为自适应哈希索引( AHI )。可以通过参数 innodb_adaptive_hash_index 来禁用或启用此特性。

小结

总体来说 InnoDB 的高性能体现在:插入数据的时候先保存在内存中,直接跟内存交互性能比较好,而且还有插入缓存优化,保证了高并发写操作。高可用则表现在两次写特性,保证了机器宕机或者出故障的时候数据不会丢失。这里只是简单介绍了一下 InnoDB 的工作流程和一些特性,当然 InnoDB 还有很多很多强大的功能,比如说事务、锁、索引、算法等等有兴趣的同学可以参考《 MySQL 技术内幕 InnoDB 存储引擎》这本书深入了解。

0

我们正在招聘Java工程师,欢迎有兴趣的同学投递简历到 rd-hr@xingren.com

发表评论

电子邮件地址不会被公开。 必填项已用*标注