InnoDB是事务安全的MySQL存储引擎,设计上采用了类似于Oracle数据的架构。通常来说,InnoDB存储引擎是OLTP应用中核心表的首选存储引擎。该存储引擎是第一个完整支持ACID事务的MySQL存储引擎,其特点是行锁设计、支持MVCC、支持外键、提供一致性非锁定读,同时被设计用来最有效的地利用内存和CPU。
InnoDB体系架构
InnoDB存储引擎有多个内存块,可以认为这些内存块组成了一个大的内存池,负责如下工作:
1:维护所有进程/线程需要访问的多个内部数据结构
2:缓存磁盘上的数据,方便快速的读取,同时在对磁盘文件的修改之前在这里缓存
3:重做日志(redo log)缓冲
后台线程的主要作用是负责刷新内存池中的数据,保证内存池中的内存缓存的是最近的数据。此外将已修改的数据文件刷新到磁盘文件,同时保证在数据库发生异常的情况下InnoDB
能恢复到正常运行状态。
后台线程
InnoDB存储引擎是多线程的模型,因此其后台有多个不同的后台线程,负责处理不同的任务。
Master Thread
Matser Thread 是一个非常核心的后台线程,InnoDB存储引擎的主要工作都是在一个单独的后台线程Master Thread中完成的,主要负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括赃页的刷新、合并插入缓冲、undo页的回收。
Master Thread具有最高的线程优先级别。其内部由多个循环组成:主循环(loop)后台循环(background loop)刷新循环(flush loop) 暂停循环(suspend loop) Master Thread 会根据
数据库运行的状态在这些循环中进行切换。
Loop 被称为主循环,因为大多数的操作是在这个循环中,主循环有两部分的操作,每秒钟的操作和每10秒的操作
每秒一次的操作包括:
1:重做日志缓冲刷新到磁盘,即使这个事务还没有提交。即使某个事务还没有提交,InnoDB存储引擎仍然每秒会将重做日志缓冲中的内容刷新到重做日志文件。这可以很好的解释为什么再大的事务提交(commit)的时间是很短的
2:合并插入缓冲(可能执行)合并缓冲(Insert Buffer)并不是每秒都会发生的。InnoDB存储引擎会判断当前一秒内发生的IO次数是否小于5次,InnoDB认为当前的IO压力很小,可以执行合并插入缓冲操作。
3:至多刷新100个InnoDB的缓冲池中的赃页到磁盘(可能执行),刷新100个赃页也不是每秒都会发生的。InnoDB存储引擎通过判断当前缓冲池中赃页的比例(buf_get_modified_ratio_pct)是否超过了配置文件中innodb_max_dirty_pages_pct这个参数(默认为90%)如果超过了这个阈值,InnoDB存储引擎认为需要做磁盘同步的操作,将100个赃页写入磁盘中。
4:如果当前没有用户活动,则切换到background loop(可能)
每10秒的操作,包括如下内容:
1:刷新100个赃页到磁盘(可能发生),
InnoDB存储引擎先判断过去10秒内磁盘的IO操作是否小于200次,如果是,InnoDB存储引擎认为当前有足够的磁盘IO操作能力,因此将赃页刷新到磁盘
2:合并至多5个插入缓冲(总是)
InnoDB存储引擎会合并插入缓冲。不同于每秒一次操作时可能发生的合并插入缓冲操作,这次的合并插入缓冲操作总会在这个阶段进行
3:将重写日志缓冲刷新到磁盘(总是)
4:删除无用的undo页 。(总是)
InnoDB存储引擎会执行full purge操作,即删除无用的Undo页。对表进行update,delete,这类操作时,原先的行被标记为删除,但因为一致性读的关系需要保留这些版本的信息。但是在full purge过程中,InnoDB存储引擎会判断当前事务系统中已被删除的行是否可以删除(比如查询操作需要读取之前版本的undo信息)如果可以删除InnDB会立即将其删除。
5:刷新100个或者10个赃页到磁盘 (总是) InnoDB存储引擎通过判断当前缓冲池中赃页的比例(buf_get_modified_ratio_pct)如果有超过70%的赃页,则刷新100个赃页到磁盘,如果赃页的比例小于70%,则只刷新10%的赃页到磁盘
background loop 当前没有用户活动(数据库空闲时)或者数据库关闭就会切换到这个循环。background loop会执行一下的操作
1:删除无用的Undo 页(总是)
2:合并20个插入缓冲(总是)
3:跳回到主循环(总是)
4:不断刷新100个页直到符合条件
IO Thread
在InnoDB存储引擎中大量使用了AIO(Async IO 异步io)来处理IO请求,这样可以极大提高数据库的性能。IO Thread 的工作主要是负责这些IO请求的回调(call back)处理
IO Thread 主要有read 、write、insert buffer和log IO。
Purge Thread
事务被提交后,其所使用的undolog可能不在需要,因此需要PurgeThread来回收已经使用并分配的undo页
内存
缓冲池
InnoDB存储是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可将其视为基于磁盘的数据库系统。在数据库系统中,由于CPU速度与磁盘速度之间的鸿沟,基于磁盘的数据库系统通常使用缓存池技术来提高数据库的整体性能。缓冲池简单的说是一块内存区域,通过内存的速速来弥补磁盘速度较慢对数据库性能的影响。在数据库中进行读取页的操作,首先将从磁盘读到的页存放在缓冲池中。下一次再读相同的页时,首先判断该页是否在缓冲池中。若在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则,读取磁盘上的页。
对于数据库中页的修改操作,则首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。这里需要注意的是,页从缓冲池刷新到磁盘的操作并不是在每次页发生更新时触发
而是通过Checkpoint的机制刷新到磁盘
缓存池中缓存的数据页类型有:索引页、数据页、undo页、插入缓冲(insert buffer) 自适应索引 、InnoDB存储的锁信息、数据字典信息等。不能简单地认为,缓冲池只是缓存索引页
和数据页,它们只是占缓冲池很大的一部分而已。
InnoDB存储引擎中内存的结构情况为
LRU List、Free List和Flush List
InnoDB存储引擎是通过LRU List ,Free List,Flush List对内存区域进行管理的。
数据库中的缓冲池是通过LRU(Latest Recent Used)算法来进行管理的。即最频繁使用的页在LRU列表的前端,而最少使用的页在LRU列表的尾端。当缓冲池不能存放新读取到的页时,将首先释放LRU列表中尾端的页。在InnoDB存储引擎中,缓冲池中页的大小默认为16kb,同样使用LRU算法对缓冲池进行管理。InnoDB存储引擎对传统的LRU算法做了一些优化。在InnoDB的存储引擎中LRU列表中还加入了midpoint位置,新读取到的页,虽然是最新访问的页,但并不是直接放到LRU列表的首部,而是放到列表的midpoint位置。在默认配置下,该位置在LRU列表长度的5/8处。
为什么不采用朴素的LRU算法,直接将读取的页放入到LRU列表的首部呢?这是因为若直接将读取到的页放入到LRU的首部,那么某些SQL操作可能会使缓冲池中的页被刷新出,从而影响缓冲池的效率。常见的这类操作为索引或数据的扫描操作。这类操作需要访问表中的许多页,甚至是全部的页,而这些页通常来说又仅在这次查询操作中需要,并不是活跃的热点数据。如果页被放入LRU列表的首部,那么非常可能将所需要的热点数据页从LRU列表中移除,而在下一次需要读取该页时,InnoDB存储引擎需要再次访问磁盘。
LRU列表用来管理已经读取的页,但当数据库刚启动时,LRU列表是空的,即没有任何页。这时页都存放在Free列表中。当需要从缓冲池中分页时,首先从Free列表中查找是否有可用的空闲页,若有则将该页从Free列表页中删除,放到LRU列表中。如果Free列表中没有空闲页则需要根据LRU算法,淘汰LRU列表末尾的页。将该内存空间分配给新的页。当页从LRU列表的old部分加入到new部分时称此时发生的操作为page made young ,
在LRU列表中的页被修改后,称该页为赃页(dirty page)即缓冲池中的页和磁盘上的页的数据产生了不一致。这时数据库会通过checkpoint机制将赃页刷新到磁盘,而flush列表中保存的为赃页列表,赃页即保存在LRU列表也保存在Flush列表中。LRU列表用来管理缓冲池中页的可用性,Flush列表用来管理将页刷新到磁盘,二者互不影响。
重做日志缓冲
InnoDB存储引擎的内存区域除了有缓冲池外,还有重做日志缓冲(redo log buffer)InnoDB存储引擎首先将重做日志信息先放入到这个缓冲区,然后按一定频率将其刷新到重做日志文件。重做日志缓冲一般不需要设置得很大,因为一般情况下每一秒钟会将重做日志缓冲刷新到日志文件,因此用户只需要保证每秒产生的事务量在这个缓冲大小之内即可。
在通常情况下,8MB的重做日志缓冲池足以满足绝大部分的应用,因为重做日志在下面三种情况下会将重做日志缓冲的内容刷新到外部磁盘的重做日志文件中
1:Master Thread 每一秒将重做日志缓冲刷新到重做日志文件
2:每个事务提交时会将重做日志缓冲刷新到重做日志文件
3:当重做日志缓冲池剩余空间小于1/2时,重做日志缓冲刷新到重做日志文件。
CheckPoint 技术
缓冲池的设计目的为了协调CPU速度与磁盘速度的鸿沟。因此页的操作首先都是在缓冲池中完成的,如果一条DML语句,如update或delete改变了页中的记录,那么此时页是赃的,即缓冲池中的页的版本要比磁盘的新,数据库需要将版本的页从缓冲池刷新到磁盘。倘若每次一个页发生变化,就将新页的版本刷新到磁盘,那么这个开销是非常大的。若热点数据集中在某几个页中,那么数据库的性能将变得非常差。同时,如果在缓冲池将页的新版本刷新到磁盘时发生了宕机,那么数据就不能恢复了。为了避免发生数据丢失的问题,当前事务采用Write Ahead Log策略,即当前事务提交时,先写重做日志(先写到重写日志缓冲中),再修改页。当由于发生宕机而导致数据丢失时,通过重做日志来回复数据。当数据库
发生宕机时,数据库不需要重做所有的日志,因为checkpoint之前的页都已经刷新回磁盘,当缓冲池不够用时,根据LRU算法会溢出最近最少使用的页,若此页为赃页,那么需要强制执行Checkpoint,将赃页刷新到磁盘。
checkPoint分为
Sharp checkPoint 发生数据库关闭时将所有的赃页都刷新到磁盘
Fuzzy checkpoint InnoDB引擎内部使用Fuzzy Checkpoint 进行页的刷新,只刷新一部分赃页,而不是刷新所有的赃页到磁盘。
Fuzzy checkPoint 又分为
Master Thread CheckPoint Master Thread中发生的checkPoint,差不多以每秒或每10秒的速度从缓冲池的赃页列表中刷新一定比例的页到磁盘
FLUSH_LRU_LIST Checkpoint LRU列表中需要有100个空闲页可供使用,如果没有100个可用空闲页,那么InnoDB存储引擎会将LRU列表尾端页移除。如果这些页中又脏页,那么需要进行checkPoint
Async/Sync Flush CheckPoint 重做日志文件不可用的情况,需要强制将一些页刷新到磁盘
Dirty Page too much Checkpoint
InsertBuffer 插入缓冲
在InnoDB存储引擎中,主键是行唯一的标识符。通常应用程序中行记录的插入顺序是按照主键递增的顺序进行插入,插入聚集索引是顺序的,不需要磁盘的随机读。如果一张表上含有辅助索引,在进行插入操作时,数据页的存放还是按主键进行顺序存放,但是对于非聚集索引叶子节点的插入不再是顺序的这时需要离散地访问非聚集索引页,由于随机读取的存在而导致了插入操作性能下降。对于非聚集索引的插入或更新操作,不是每一次直接插入到索引中,而是判断插入的非聚集索引页是否在缓冲池中,若在,则直接插入;若不在则先放入到insert buffer对象中。然后再以一定的频率进行Insert Buffer和辅助索引页子节点的合并操作,这时通常能将多个插入合并到一个操作中,这大大提高了对于非聚集索引插入的性能。
使用insert buffer需要满足如下两个条件
1:索引是辅助索引(二级索引)
2:索引不是唯一的
InnoDB的关键特性
1:插入缓冲
2: 两次写 对缓冲池的赃页进行刷新时,并不写入磁盘,而是通过memcpy函数将赃页先复制到内存中的doublewrite buffer 之后通过doublewrite buffer 再分两次,每次1MB顺序写入共享表空间的物理磁盘上(重做日志),然后马上调用fsync函数同步磁盘
3:异步IO
4:自适应哈希索引,当某些查询经常用到,InnoDB引擎会自动创建自适应索引
5: 刷新邻接页 当刷新赃页到磁盘时,判断邻接页是否为赃页,如果为赃页,则刷新到磁盘。