InnoDB体系结构

注:本文内容摘自《MySQL技术内幕 InnoDB存储引擎》

概述

MySQL区别于其他数据库最重要的特点是其插件式的表存储引擎。MySQL存储引擎支持很多种类,最为出名的就是InnoDB。
InnoDB由Innobase Oy公司开发,创始人芬兰赫尔辛基的Heikki Tuuri,InnoDB存储引擎支持事务,主要面向OLTP应用。其特点有行锁设计、支持外键,支持类似Oracle的非锁定读,即默认情况下读操作不会产生锁。InnoDB将数据放在一个逻辑的表空间中,从MySQL 4.1版本开始,它可以将每个InnoDB的表单独存放到一个独立的ibd文件中。InnoDB通过使用多版本并发控制MVCC来获得高并发性,并且实现4种隔离级别,默认为REPEATABLE READ级别。InnoDB还提供插入缓冲、二次写、自适应哈希索引、预读等高性能和高可用功能。对于表中的数据存储,采用聚集(clustered)的方式,类似于Oracle的索引聚集表(index organized table,IOT)。每个表的存储都按主键的顺序存放,如果没有显式指定主键,将为每一行生成一个6字节的ROWID并以此作为主键。

InnoDB体系结构

InnoDB存储引擎的架构与Oracle很类似,包括一个比较大的内存池(类似于Oracle的SGA),以及多个后台线程。大致的结构可以用下图表示:
InnoDB体系结构_第1张图片
下面具体看一下内存池及线程包含的内容。

线程

Oracle的核心后台进程有CKPT、DBWn、LGWR、ARCn、PMON、SMON等,而InnoDB存储引擎的主要工作是在一个master线程上完成的。默认情况下,InnoDB存储引擎的后台线程有7个:4个IO线程,1个master线程,1个锁监控线程,1个错误监控线程
4个IO线程分别是insert buffer thread、log thread、read thread、write thread。在某些版本中,read thread和write thread可以通过参数进行调整,比如增大到4个。可以通过show variables命令查看参数的默认值。

mysql> show engine innodb status;
mysql> show variables like 'innodb_%io_threads';

内存

InnoDB存储引擎的内存由几个部分组成:缓冲池(buffer pool)、重做日志缓冲池(redo log buffer)以及额外的内存池(additional memory pool),分别由参数innodb_buffer_pool_size、innodb_log_buffer_size、innodb_additional_mem_pool_size决定。
(1)缓冲池是占最大块内存的部分,用来存放各种数据的缓存。InnoDB工作方式总是将数据库文件按页(每页16K)读取到缓冲池,然后按最近最少使用(LRU)算法保留在缓冲池中的缓存数据。如果数据库文件需要修改,总是首先修改在缓冲池中的页(修改后称为脏页),然后按照一定的频率将缓冲池的脏页刷新到文件。可以通过show engine innodb status来查看innodb_buffer_pool的具体使用情况,这个命令显示的是过去24秒内的数据库状态。
具体来看,缓冲池中缓存的数据页类型有:索引页、数据页、undo页、插入缓冲(insert buffer)、自适应哈希索引(adaptive hash index)、锁信息(lock info)、数据字典信息(data dictionary)等。所以不能简单的认为缓冲池只是缓存索引页和数据页。
InnoDB存储引擎的内存结构可由下图表示:
InnoDB体系结构_第2张图片
(2)日志缓冲将重做日志信息先放入这个缓冲区,然后按一定频率将其刷新到重做日志文件。该值一般不需要很大,因为一般情况下每秒钏就会将重做日志缓冲刷新到日志文件,因此只需要保证每秒产生的事务量在这个缓冲大小之内即可。
(3)额外的内存池也是重要的。InnoDB对内存的管理是通过一种称为内存堆(heap)的方式进行。在对一些数据结构本身分配内存时,需要从额外的内存池中申请,当该区域内存不够时会从缓冲池中申请。InnoDB实例会申请缓冲池的空间,但是每个缓冲池中的帧缓冲(frame buffer)还有对应的缓冲控制对象,而且这些对象记录了诸如LRU、锁、等待等方面的信息,而这个对象的内存需要从额外内存池中申请。因此,当申请很大的InnoDB缓冲池时,额外的内存池也应该相应增加。

master thread

master thread完成了InnoDB的主要工作,其内部由几个循环组成:主循环、后台循环(background loop)、刷新循环(flush loop)、暂停循环(suspend loop)。master thread会根据数据库运行的状态在这几个循环中进行切换。

loop主循环

大多数操作都在这个循环中,其中有两大部分操作:每秒钟的操作和每10秒的操作
每秒一次的操作包括

  • 日志缓冲刷新到磁盘,即使这个事务还没有提交(总是)。
  • 合并插入缓冲(可能)。-根据当前一少IO次数是否小于5次。
  • 至多刷新100个InnoDB的缓冲池中的脏页到磁盘(可能)。-根据脏页比例是否超过90%决定。
  • 如果当前没有用户活动,切换到background loop(可能)。

即使事务还没提交,InnoDB也会每秒一次将重做日志缓冲中的内容刷新至重做日志文件,这也是为什么再大的事务commit时间也很快。
合并插入缓冲(insert buffer)并不是每秒发生。InnoDB会判断当前一秒内发生的IO次数是否小于5次,如果小于5次,认为当前IO压力很小,可以执行合并插入缓冲的操作。
刷新100个脏页也不是每秒发生。InnoDB判断当前缓冲池中脏页的比例是否超过配置文件中的innodb_max_dirty_pages_pct(默认90%),如果超过阈值,认为需要做磁盘同步操作,将100个脏页写入磁盘。

每10秒的操作包括:

  • 刷新100个脏页到磁盘(可能)。-根据过去10秒IO操作次数是否小于200次决定。
  • 合并至多5个插入缓冲(总是)。
  • 将日志缓冲刷新到磁盘(总是)。
  • 删除无用的Undo页(总是)。
  • 刷新100个或10个脏页到磁盘(总是)。-根据脏页比例是否达到70%决定是100个还是10个。
  • 产生一个检查点(总是)。

InnoDB先判断过去10秒内磁盘的IO操作是否小于200次,如果是,认为当前有足够的磁盘IO操作能力,因此将100个脏页刷新到磁盘。接着InnoDB会合并插入缓冲,不同于每1秒的可能发生,这次的合并插入缓冲操作总是会进行的。之后InnoDB会再执行一次将日志缓冲刷新到操作。
接着InnoDB会执行一步full purge操作,删除无用的Undo页。InnoDB会判断当前事务系统中已被删除的行是否可以删除,比如有时候可能还有查询操作需要读取之前版本的Undo信息,如果可以就立即将其删除。从源码看每次最多删除20个Undo页。
然后InnoDB会判断缓冲池中脏页的比例,比如超过70%的脏页,则刷新100个脏页到磁盘,如果小于70%,则只刷新10%的脏页到磁盘。
最后,InnoDB会产生一个检查点(checkpoint)。InnoDB存储引擎在checkpoint时并不会把所有缓冲池中的脏页都写入磁盘,因为这样对性能可能产生影响,而只是将最老日志序列号(oldest LSN)的页写入磁盘。

background loop循环

如果当前没有用户活动,即数据库空闲或数据库关闭时,就会切换到这个循环。此循环执行以下操作:

  • 删除无用的Undo页(总是)。
  • 合并20个插入缓冲(总是)。
  • 跳回到主循环(总是)。
  • 不断刷新100个页,直到符合条件(可能,跳转到flush loop中完成)。

flush loop循环

顾名思义,执行flush相关操作。

suspend loop暂停循环

如果flush loop中也没什么事情做,InnoDB会切换到暂停循环,将master thread挂起,等待事件的发生。如果启动InnoDB存储引擎但没有任何表,那么master thread总是处于挂起状态。

以上是InnoDB master thread的相关机制,从介绍里面也看出,中间有一些潜在问题的。包括每次最多刷新100个脏页,合并20个插入缓冲。对于密集写的应用里面,master thread可能不够快。因此后面在InnoDB Plugin中对master thread又进行了诸多的优化。

你可能感兴趣的:(数据库,MySQL,oracle,数据库,java)