mysql之InnoDB存储引擎(1)——InnoDB存储引擎体系

一、innoDB体系架构

mysql之InnoDB存储引擎(1)——InnoDB存储引擎体系_第1张图片

InnoDB存储引擎体系结构

1、后台线程:

        后台线程的主要作用是负责刷新内存池中的数据,保证缓冲池中的内存缓存的是最近的数据。此外将已修改的数据文件刷新到磁盘文件,同时保证在数据库发生异常的情况下innodb能恢复到正常运行状态。

1.1、Master Thread

        主要负责将缓冲池的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新、合并插入缓冲(INSERT BUFFER)、UNDO页的回收等。

1.2、IO Thread

        在InnoDB存储引擎中大量使用了AIO(异步IO)来处理写IO请求,这样做极大提高了数据库的性能,而IO Thread的工作主要是负责这些io请求的回调处理处理。IO Thread包括:write、read、insert buffer和log IO thread。

1.3、purge Thread

        事务被提交后,其所使用的undolog可能不再需要,因此需要purge Thread 来回收已经使用并分配的undo页。可以在mysql配置文件中添加以下命令来启用独立的purge Thread线程:

        [mysqld]

                innodb_purge_threads=1

        从1.2版本开始,innodb支持多个purgethread,为了进一步加快undo页的回收,

1.4、page cleaner Thread

        其作用是将之前版本的脏页的刷新操作都放到单独的线程来完成,是为了减轻Master Thread的工作及对于用户查询线程的阻塞,进一步提高InnoDB存储引擎的性能。

2、内存:

2.1、缓冲池

        Innodb存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理,因此可将其视为基于磁盘对的数据库系统,由于cpu和磁盘之间速度的差距,所以基于磁盘的数据库系统通常使用缓冲池技术来提高数据库的性能,

        缓冲池简单来说就是一块内存区域,通过内存的速度来弥补磁盘速度较慢对于数据库性能的影响。在数据库中进行读取页的操作,首先将从磁盘读取到页存放在缓冲池中,下一次在读相同的页时,首先判断该页是否在缓冲池中,在则命中,直接读取,不在则读取磁盘上的页,

        对于数据库中页的修改操作,则首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。通过一种Checkpoint的机制刷新会磁盘。

        缓冲池中数据页类型有:索引页、数据页、undo页、插入缓冲、自适应哈希索引、innodb存储的锁信息、数据字典信息等。

2.2、LRU list、Free list和flush list

        数据库中的缓冲池是通过LRU(最近最少使用)算法来进行管理的。

        LRU列表用来管理已经读取的页,但当数据库刚启动是,LRU列表是空的,即没有任何的页。这时页都存放在Free 列表中。当需要从缓存池中分页时,首先从Free列表中查找是否有可用的空闲页,若有则将该页从free列表中删除,放入LRU列表中。LRU列表中old 变为 new ,称为page made young,而因为inndb_old_blocks_time的设置而导致页没有从old部分移动到new部分的操作称为page not made young。

        在LRU列表中的页被修改后,称该页为脏页,即缓冲池中的页和磁盘上的页的数据产生了不一致,这时数据库会通过 CHECKPOINT机制将脏页刷新回磁盘,而FLUSH列表中页即为脏页列表,脏页即可能存在于LRU列表中,也有可能存在于FLUSH列表中,LRU列表用来管理缓冲池中页的可用性,FLUSH列表用来管理将页刷新回磁盘,

2.3、重做日志缓冲

        InnoDB存储引擎的内存区域除了缓冲池外,还有重做日志缓冲,首先将重做日志信息先放入到这个缓冲区,然后按照一定的频率 将其刷新到重做日志文件,缓冲不用设置的很大,因为一般每一秒钟会将重做的日志缓冲刷新到日志文件,缓冲区大小可由参数innodb_log_buffer_size参数控制,默认为8MB。

        有一下三种情况能够触发重做日志缓存刷新到重做日志文件中:

                每一秒,master thread会将重做日志缓存刷新到重做日志文件

                每个事务提交前,会将重做日志缓存刷新到重做日志文件中。

                当重做日志缓存剩余空间小于1/2时,重做日志缓存刷新到重做日志文件。

 2.4、额外的内存池

        在innodb存储引擎中,对内存的管理是通过一种称为内存堆的方式进行的,在对一些数据结构本身的内存进行分配时,需要从额外的内存池中进行申请,当该区域内存不够时,会从缓冲池中进行申请。

 二、Checkpoint技术

        为了避免发生数据丢失的问题,当前事务数据库系统普遍都采用了Write Ahead Log策略,即当事务提交时,先写重做日志,再修改页。当由于发生宕机而导致数据丢失时,通过重做日志来完成数据的恢复。

        当数据库发生宕机时,数据库不需要重做所有的日志,因为Checkpoint之前的页都已经刷新回磁盘。故数据库只需对Checkpoint后的重做日志进行恢复。

        此外,当缓冲池不够用时,根据LRU算法会溢出最近最少使用的页,若此页为脏页,那么需要强制执行Checkpoint,将脏页也就是页的新版本刷回磁盘。

        重做日志可以被重用的部分是指这些重做日志已经不再需要,即当数据库发生宕机时,数据库恢复操作不需要这部分的重做日志,因此这部分就可以被覆盖重用。若此时重做日志还需要使用,那么必须强制产生Checkpoint,将缓冲池中的页至少刷新到当前重做日志的位置。

        在InnoDB存储引擎内部,有两种Checkpoint,分别为:

                Sharp Checkpoint

                Fuzzy Checkpoint
        Sharp Checkpoint发生在数据库关闭时将所有的脏页都刷新回磁盘,这是默认的工作方式,即参数innodb_fast_shutdown=1。 故在InnoDB存储引擎内部使用Fuzzy Checkpoint进行页的刷新,即只刷新一部分脏页,而不是刷新所有的脏页回磁盘。 在InnoDB存储引擎中可能发生如下几种情况的Fuzzy Checkpoint:

                Master Thread Checkpoint

                FLUSH_LRU_LIST Checkpoint

                Async/Sync Flush Checkpoint

                Dirty Page too much Checkpoint

        FLUSH_LRU_LIST Checkpoint是因为InnoDB存储引擎需要保证LRU列表中需要有差不多100个空闲页可供使用。在InnoDB 1.1.x版本之前,需要检查LRU列表中是否有足够的可用空间操作发生在用户查询线程中,显然这会阻塞用户的查询操作。而在MySQL 5.6版本,也就是InnoDB 1.2.x版本开始,这个检查被放在了一个单独的Page Cleaner线程中进行,并且用户可以通过参数innodb_lru_scan_depth控制LRU列表中可用页的数量,该值默认为1024。

        Async/Sync Flush Checkpoint指的是重做日志文件不可用的情况,这时需要强制将一些页刷新回磁盘,而此时脏页是从脏页列表中选取的。Async/Sync Flush Checkpoint是为了保证重做日志的循环使用的可用性。在InnoDB 1.2.x版本之前,Async Flush Checkpoint会阻塞发现问题的用户查询线程,而Sync Flush Checkpoint会阻塞所有的用户查询线程,并且等待脏页刷新完成。从InnoDB 1.2.x版本开始——也就是MySQL 5.6版本,这部分的刷新操作同样放入到了单独的Page CleanerThread中,故不会阻塞用户查询线程。

        Dirty Page too much Checkpoint,即脏页的数量太多,导致InnoDB存储引擎强制进行Checkpoint,其目的总得来说还是为了保证缓冲池中有足够可用的页。

三、Master Thread工作方式

1、InnoDB 1.0.x版本之前的MasterThread

        Master Thread具有最高的线程优先级别。其内部由多个循环(loop)组成:主循环(loop)、后台循环(background loop)、刷新循环(flush loop)、暂停循环(suspend loop)。Master Thread会根据数据库的运行的状态在loop、background loop、flush loop和suspend loop中进行切换。

        Loop被称为主循环,因为大多数的操作是在这个循环中,其中有两大部分的操作——每秒钟的操作和每10秒钟的操作。

        每秒一次的操作包括:

                日志缓冲刷新到磁盘,即使这个事务还没有提交(总是)

                合并插入缓冲(可能)

                至多刷新100个InnoDB的缓冲池中的脏页到磁盘(可能)

                如果当前没有用户活动,则切换到background loop(可能)

        即使某个事务还没有提交,InnoDB存储引擎仍然每秒会将重做日志缓冲中的内容刷新到重做日志文件。这一点是必须要知道的,因为这很好地解释为什么再大的事务提交(commit)的时间也是很短的。

        合并插入缓存时,InnoDB存储引擎会判断当前一秒内发生的IO次数是否小于5次,如果小于5次,InnoDB认为当前的IO压力很小,可以执行合并插入缓冲的操作。

        InnoDB存储引擎通过判断当前缓冲池中脏页的比例(buf_get_modified_ratio_pct)是否超过了配置文件中innodb_max_dirty_pages_pct这个参数(默认为90,代表90%),如果超过了这个阈值,InnoDB存储引擎认为需要做磁盘同步的操作,将100个脏页写入磁盘。 接着来看每10秒的操作,包括如下内容:

                刷新100个脏页到磁盘(可能的情况下)

                合并至多5个插入缓冲(总是)

                将日志缓冲刷新到磁盘(总是)

               删除无用的Undo页(总是)

                刷新100个或者10个脏页到磁盘(总是)

        在以上的过程中,InnoDB存储引擎会先判断过去10秒之内磁盘的IO操作是否小于200次,如果是,InnoDB存储引擎认为当前有足够的磁盘IO操作能力,因此将100个脏页刷新回磁盘。

        对表进行update、delete这类操作时,原先的行被标记为删除,但是因为一致性读(consistent read)的关系,需要保留这些行版本的信息。但是在fullpurge过程中,InnoDB会判断当前事务系统中已被删除的行是否可以删除,比如有时候可能还有查询操作需要读取之前版本的undo信息,如果可以删除,InnoDB会立即将其删除。从源代码中可以发现,InnoDB存储引擎在执行full purge操作时,每次最多尝试回收20个undo页。

        然后,InnoDB存储引擎会判断缓冲池中脏页的比例(buf_get_modified_ratio_pct),如果有超过70%的脏页,则刷新100个脏页到磁盘,如果脏页的比例小于70%,则只需刷新10%的脏页到磁盘。

        接着来看background loop,若当前没有用户活动(数据库空闲时)或者数据库关闭(shutdown),就会切换到这个循环。background loop会执行以下操作:

                删除无用的Undo页(总是)

                合并20个插入缓冲(总是)

               跳回到主循环(总是)(应该不是总是吧?)

                不断刷新100个页直到符合条件(可能,跳转到flush loop中完成)

        若flush loop中也没有什么事情可以做了,InnoDB会切换到suspend loop,将Master Thread挂起,等待事件的发生。

2、InnoDB 1.2.x版本之前的MasterThread

        即使磁盘能在1秒内处理多于100个页的写入和20个插入缓冲的合并,但是由于hard coding,Master Thread也只会选择刷新100个脏页和合并20个插入缓冲。同时,当发生宕机需要恢复时,由于很多数据还没有刷新回磁盘,会导致恢复的时间可能需要很久,尤其是对于insert buffer来说。

        因此InnoDB Plugin(从InnoDB 1.0.x版本开始)提供了参数innodb_io_capacity,用来表示磁盘IO的吞吐量,默认值为200。对于刷新到磁盘页的数量,会按照innodb_io_capacity的百分比来进行控制。规则如下:

在合并插入缓冲时,合并插入缓冲的数量为innodb_io_capacity值得5%

在从缓冲区刷新脏页时,刷新脏页的数量为innodb_io_capacity

        而从InnoDB 1.0.x版本开始,innodb_max_dirty_pages_pct默认值变为了75,和Google测试的80比较接近。这样既可以加快刷新脏页的频率,又能保证了磁盘IO的负载。

        引入innodb_adaptive_flushing参数,该值影响每秒刷新脏页的数量。刷新规则是:脏页在缓冲池中所占的比例小于innodb_max_dirty_pages_pct时,不刷新脏页;大于innodb_max_dirty_pages_pct时,刷新100个脏页。InnoDB存储引擎会通过一个名为buf_flush_get_desired_flush_rate的函数来判断需要刷新脏页最合适的数量,通过判断产生重做日志的速度来决定最合适的刷新脏页数量。

        还有一个改变是:之前每次进行full purge操作时,最多回收20个Undo页,从InnoDB 1.0.x版本开始引入了参数innodb_purge_batch_size,该参数可以控制每次full purge回收的Undo页的数量。该参数的默认值是20,并可以动态地对其进行修改。

 四、InnoDB关键特性

        InnoDB的关键特性包括:

                插入缓冲(Insert Buffer)

                两次写(Double Write)

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

                异步IO(Async IO)

                刷新邻接页(Flush Neighbor Page)

1、插入缓冲

1.1、Insert Buffer

        其实不然,InnoDB缓冲池中有Insert Buffer信息固然不错,但是Insert Buffer和数据页一样,也是物理页的一个组成部分。

CREATE TABLE t {
    a INT AUTO_INCREMENT,
    b VARCHAR(30),
    PRIMARY KEY(a),
    key(b)
);

        其中a列是自增长的,若对a列插入NULL值,则由于其具有AUTO_INCREMENT属性,其值会自动增长。同时页中的记录按a的值进行顺序存放。在一般情况下,不需要随机读取另一个页中的记录。因此,对于这类情况的插入操作,速度是非常快的。

        并不是所有的主键插入都是顺序的。若主键类是UUID这样的类,那么插入和辅助索引一样,同样是随机的。即使主键是自增类型,但是插入的是指定的值,而不是NULL值,那么同样可能导致插入并非连续的情况。

        但是不可能每张表上只有一个聚集索引,更多情况下,一张表上有多个非聚集的辅助索引(secondary index)。比如,用户需要按照b这个字段进行查找,并且b这个字段不是唯一的。

        在进行插入操作时,数据页的存放还是按主键a进行顺序存放的,但是对于非聚集索引叶子节点的插入不再是顺序的了,这时就需要离散地访问非聚集索引页,由于随机读取的存在而导致了插入操作性能下降。当然这并不是这个b字段上索引的错误,而是因为B+树的特性决定了非聚集索引插入的离散性。

        InnoDB开创性地设计了Insert Buffer,对于非聚集索引的插入或更新操作,不是每一次直接插入到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,若在,则直接插入;若不在,则先放入到一个Insert Buffer对象中,好似欺骗。数据库这个非聚集的索引已经插入到叶子节点,而实际并没有,只是存放在另一个位置。然后再以一定的频率和情况进行Insert Buffer和辅助索引页子节点的merge操作,这时通常能将多个插入合并到一个操作中(因为在一个索引页中),这就大大提高了对于非聚集索引插入的性能。

        然而Insert Buffer的使用需要同时满足以下两个条件:

                索引是辅助索引(secondary index)

                索引不是唯一(unique)的

        辅助索引不能是唯一的,因为在插入缓冲时,数据库并不去查找索引页来判断插入的记录的唯一性。

1.2、Change Buffer

        InnoDB存储引擎可以对DML操作——INSERT、DELETE、UPDATE都进行缓冲,他们分别是:Insert Buffer、Delete Buffer、Purge Buffer。

        当然和之前Insert Buffer一样,Change Buffer适用的对象依然是非唯一的辅助索引。

        因为Delete Buffer对应UPDATE操作的第一个过程,即将记录标记为删除。Purge Buffer对应UPDATE操作的第二个过程,即将记录真正的删除。

        innodb_change_buffer_max_size值默认为25,表示最多使用1/4的缓冲池内存空间。而需要注意的是,该参数的最大有效值为50。

1.3. Insert Buffer的内部实现

        可能令绝大部分用户感到吃惊的是,Insert Buffer的数据结构是一棵B+树。在MySQL 4.1之前的版本中每张表有一棵Insert Buffer B+树。而在现在的版本中,全局只有一棵Insert Buffer B+树,负责对所有的表的辅助索引进行InsertBuffer。

        非页节点存放的是查询的search key(键值),其构造是:| space | marker |offset |。

1.4. Merge Insert Buffer

        概括地说,Merge Insert Buffer的操作可能发生在以下几种情况下:

        辅助索引页被读取到缓冲池时

         Insert Buffer Bitmap页追踪到该辅助索引页已无空间可用时

        第一种情况为当辅助索引页被读取到缓冲池中时,例如这在执行正常的SELECT查询操作,这时需要检查Insert Buffer Bitmap页,然后确认该辅助索引页是否有记录存放于Insert Buffer B+树中。若有,则将Insert Buffer B+树中该页的记录插入到该辅助索引页中。可以看到对该页多次的记录操作通过一次操作合并到了原有的辅助索引页中,因此性能会有大幅提高。

        Insert Buffer Bitmap页用来追踪每个辅助索引页的可用空间,并至少有1/32页的空间。若插入辅助索引记录时检测到插入记录后可用空间会小于1/32页,则会强制进行一次合并操作,即强制读取辅助索引页,将Insert BufferB+树种该页的记录及待插入的记录插入到辅助索引页中。

2、两次写

        doublewrite由两部分组成,一部分是内存中的doublewrite buffer,大小为2MB,另一部分是物理磁盘上共享表空间中连续的128个页,即2个区(extent),大小同样为2MB。在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是会通过memcpy函数将脏页先复制到内存中的doublewrite buffer,之后通过doublewrite buffer再分两次,每次1MB顺序地写入共享表空间的物理磁盘上,然后马上调用fsync函数,同步磁盘,避免缓冲写带来的问题。在这个过程中,因为doublewrite页是连续的,因此这个过程是顺序写的,开销并不是很大。在完成doublewrite页的写入后,再将doublewrite buffer中的页写入各个表空间文件中,此时的写入则是离散的。

        如果操作系统在将页写入磁盘的过程中发生了崩溃,在恢复过程中,InnoDB存储引擎可以从共享表空间中的doublewrite中找到该页的一个副本,将其复制到表空间文件,再应用重做日志。

        有些文件系统本身就提供了部分写失效的防范机制,如ZFS文件系统。在这种情况下,用户就不要启用doublewrite了。

3、自适应哈希索引

        InnoDB存储引擎会自动根据访问的频率和模式来自动地为某些热点页建立哈希索引。

        访问模式一样指的是查询的条件一样,若交替进行上述两种查询,那么InnoDB存储引擎不会对该页构造AHI。此外AHI还有如下的要求:

                以该模式访问了100次

                页通过该模式访问了N次,其中N=页中记录*1/16

4、异步IO

        与AIO对应的是Sync IO,即每进行一次IO操作,需要等待此次操作结束才能继续接下来的操作。用户可以在发出一个IO请求后立即再发出另一个IO请求,当全部IO请求发送完毕后,等待所有IO操作的完成,这就是AIO。

        AIO的另一个优势是可以进行IO Merge操作,也就是将多个IO合并为1个IO,这样可以提高IOPS的性能。

        在InnoDB存储引擎中,read ahead方式的读取都是通过AIO完成,脏页的刷新,即磁盘的写入操作则全部由AIO完成。

5、刷新邻接页

        其工作原理是:当刷新一个脏页时,InnoDB存储引擎会检测该页所在区(extent)的所有页,如果是脏页,那么一起进行刷新。这样做的好处显而易见,通过AIO可以将多个IO写入操作合并为一个IO操作,故该工作机制在传统机械磁盘下有着显著的优势。

        对于传统机械硬盘建议启用该特性,而对于固态硬盘有着超高的IOPS性能的磁盘,则建议将该参数设置为0,即关闭此特性。

五、启动、关闭与恢复

        在关闭时,参数innodb_fast_shutdown影响着表的存储引擎为InnoDB的行为。该参数可取值为0,1,2,默认值为1。

                0表示在MySQL数据库关闭时,InnoDB需要完成所有的full purge和merge insert buffer,并且将所有的脏页刷新回磁盘。这需要一些时间,有时甚至需要几个小时来完成。如果在进行InnoDB升级时,必须将这个参数设置为0,然后再关闭数据库。

                1是参数innodb_fast_shutdown的默认值,表示不需要完成上述的full purge和merge insert buffer操作,但是在缓冲池中的一些数据脏页还是会刷新回磁盘。

                2表示不完成full purge和mergeinsert buffer操作,也不将缓冲池中的数据脏页写回磁盘,而是将日志都写入日志文件。这样不会有任何事务的丢失,但是下次MySQL数据库启动时,会进行恢复操作(recovery)。

        参数innodb_force_recovery影响了整个InnoDB存储引擎恢复的状况。该参数值默认为0,代表当发生需要恢复时,进行所有的恢复操作,当不能进行有效恢复时,如数据页发生了corruption,MySQL数据库可能发生宕机(crash),并把错误写入错误日志中去。

        参数innodb_force_recovery还可以设置为6个非零值:1~6。大的数字表示包含了前面所有小数字表示的影响:

        1(SRV_FORCE_IGNORE_CORRUPT):忽略检查到的corrupt页

        2(SRV_FORCE_NO_BACKGROUND):阻止Master Thread线程的运行,如Master Thread线程需要进行full purge操作,而这会导致crash

        3(SRV_FORCE_NO_TRX_UNDO):不进行事务的回滚操作

        4(SRV_FORCE_NO_IBUF_MERGE):不进行插入缓冲的合并操作

        5(SRV_FORCE_NO_UNDO_LOG_SCAN):不查看撤销日志(Undo Log),InnoDB存储引擎会将未提交的事务视为已提交。

        6(SRV_FROCE_NO_LOG_REDO):不进行前滚的操作。

        需要注意的是,在设置了参数innodb_force_recovery大于0后,用户可以对标进行select、create和drop操作,但insert、update和delete这类DML操作是不允许的。 


你可能感兴趣的:(MySQL-InnoDB)