在InnoDB存储引擎中,缓冲池中页的大小为16KB,同样使用LRU算法对缓冲进行管理。但进行了一些优化,在InnoDB的存储引擎中,LRU列表中还加入了midpoint位置(LRU队列5/8处)。新读取到的页,虽然是最新访问的页,但是不直接放在LRU列表的首部,而是放在midpoint位置。这个算法在InnoDB存储引擎下称为midpoint insertion strate。把midpoint之后的页称为old列表,之前的称为new列表。
InnoDB存储引擎中,引入innodb_old_blocks_time来设置页读取到mid位置之后需要等待多久才会被加入到LRU列表的热端。
当数据库刚启动时,LRU列表为空。这时页都存放在Free列表中,当需要从缓冲池中分页时,首先从Free列表中查找是否有可用的空闲页,若有则将该页从Free列表中删除,放在LRU列表中。否则,根据LRU列表,淘汰LRU末尾的页,并将该页分配给新的页。
当页从LRU列表old区进入new区时,称为page made young。
由于innodb_old_blocks_time存在而导致页没有从old部分移动到new部分,称为page not made young。
当LRU列表中页被修改后,称该页为脏页,即缓冲池中的页和磁盘上的页的数据不一致。这时将通过CHECKPOINT机制将脏页刷新回磁盘。而FLUSH列表中的页即为脏页列表。
void master_thread() {
loop:
for(int i=0; i<10; i++) {
do thing once per second
sleep 1 second if necessary
}
do things once per then seconds
goto loop;
}
概述
InnoDB设计主要目标是面向在线事务处理(OLTP)的应用。 InnoDB存储引擎将数据放在一个逻辑的表空间中,这个表空间就像一个黑盒一样由InnoDB存储引擎自身管理。它可以将每个InnoDB存储引擎的表单独存放在一个独立的ibd文件中。此外支持裸设备用来建立表空间。 InnoDB通过使用多版本并发控制(MVCC)来获取高并发性,并且实现了SQL标准的四种隔离级别,默认为REPEATABLE级别。同时使用next-keylocking的策略来避免幻读现象的产生。此外,还提供了插入缓冲,二次写,自适应哈希索引,预读等高性能和高可用功能。 对于表中数据的存储,InnoDB存储引擎采用了聚集的方式,因此每张表的存储都是按主键的顺序进程存放的。如果没有显示地为表定义一个主键,InnoDB存储引擎会为每一行生成一个6字节的ROWID,并以此为主键。
架构
后台线程
InnoDB存储引擎是多线程模型,因此后台有多个不同的后台线程,负责处理不同的任务。
内存
缓冲池
InnoDB是基于磁盘存储的,并将其中的记录按照页的方式进行管理。通过缓冲池来缓解磁盘和CPU速度的问题。 缓冲池简单说是一个内存区域,通过内存的速度来弥补磁盘速度较慢对数据库的影响。
InnoDB允许有多个缓冲池实例,每个页根据哈希值不同平均分配到不同缓冲池实例中。
LRU List, Free List和Flush List
在InnoDB存储引擎中,缓冲池中页的大小为16KB,同样使用LRU算法对缓冲进行管理。但进行了一些优化,在InnoDB的存储引擎中,LRU列表中还加入了midpoint位置(LRU队列5/8处)。新读取到的页,虽然是最新访问的页,但是不直接放在LRU列表的首部,而是放在midpoint位置。这个算法在InnoDB存储引擎下称为midpoint insertion strate。把midpoint之后的页称为old列表,之前的称为new列表。 InnoDB存储引擎中,引入innodb_old_blocks_time来设置页读取到mid位置之后需要等待多久才会被加入到LRU列表的热端。 当数据库刚启动时,LRU列表为空。这时页都存放在Free列表中,当需要从缓冲池中分页时,首先从Free列表中查找是否有可用的空闲页,若有则将该页从Free列表中删除,放在LRU列表中。否则,根据LRU列表,淘汰LRU末尾的页,并将该页分配给新的页。 当页从LRU列表old区进入new区时,称为page made young。 由于innodb_old_blocks_time存在而导致页没有从old部分移动到new部分,称为page not made young。 当LRU列表中页被修改后,称该页为脏页,即缓冲池中的页和磁盘上的页的数据不一致。这时将通过CHECKPOINT机制将脏页刷新回磁盘。而FLUSH列表中的页即为脏页列表。
重做日志缓冲
InnoDB首先将重做日志信息放入这个缓冲区,然后按一定频率将其刷新到重做日志文件。 重做日志在下列三种情况下会讲重做缓冲中的内容刷新到外部磁盘的重做日志文件中。
额外内存池
对一些数据结构本身的内存进行分配时,优先从该区域进行分配,当该区域不够时,从线程池申请。
Checkpoint技术
当缓冲池中某个页变为脏页时,需要将新版本的页从缓冲池刷新回磁盘。倘若每次页变化都刷新磁盘,回影响数据库性能。并且如果在刷新过程中宕机,那么数据无法恢复,因此数据库采用Write Ahead Log策略,当事务进行提交时,先写重做日志,然后修改页。当数据库宕机时,可以从重做日志中恢复。 Checkpoint用于解决以下问题:
当数据库宕机时,数据库不需要重做所有的日志,因为checkpoint之前的页都已经被刷新回磁盘。数据库只需对checkpoint之后的重做日志进行恢复。 当缓冲池不够用时,根据LRU算法会将溢出的脏页强制执行Checkpoint ,将脏页刷新回磁盘。 重做日志不可用是因为数据库系统对重做日志的设计是循环使用的,并不让其无限增大。重做日志可以被重用的部分是指该部分重做日志不再被需要,即当数据库宕机时,数据库恢复操作不需要这部分重做日志。若此时仍需要使用,那么必须强制使用Checkpoint,将缓冲池中的页刷新到重做日志的位置。 InnoDB通过LSN(Log Sequence Number)来标记版本的。LSN是8字节数字。每个页,重做日志和Checkpoint都要LSN。 根据每次刷新页数,页的选取及触发时间,将Checkpoint分为以下两种:
发生Fuzzy Checkpoint情形:
Master Thread 工作方式
InnoDB 1.0.x版本之前的Master Thread
Master Thread 具有最高的线程优先级别。内部由多个loop组成。Master Thread根据数据库运行状态在各个循环中切换。
主循环(loop)
Loop主循环(大多数操作都在这个循环下)这个由两大部分操作,每秒和每10秒操作:
后台循环(background loop)
若当前没有用户活动(数据库空闲时)或者数据库关闭(shutdown)。
刷新循环(flush loop)
如果fulsh loop 页没有什么事情可以做,InnoDB存储引擎会切换到suspend loop
暂停循环(suspend loop)
将Master Thread挂起
InnoDB 1.2.x版本之前的Master Thread
InnoDB 1.2.x版本的Master Thread
Master Thread的伪代码如下:
其中srv_master_do_idle_tasks()就是之前版本中每10秒的操作,srv_master_do_active_tasks()处理的是之前每秒中的操作。同时对于刷新脏页的操作,从Master Thread线程分离到一个单独的Page Cleaner Thread,从而减轻了Master Thread的工作,同时进一步提高了系统的并发性。
InnoDB关键特性
插入缓冲
Insert Buffer
Insert Buffer和数据页一样,也是物理页的一个组成部分。 在InnoDB中,主键是行的唯一标识。通常应用程序中行记录的插入顺序是按照主键递增进行插入的。因此,插入聚集索引(Primary Key)一般是顺序的,不需要磁盘的随机读取。 如下定义的SQL:
其中a列是自增的,若对a插入NULL 值,则由于其AUTO_INCREAMENT属性,其值是会自动增长的。同时页中记录按a的值进行顺序存放。在一般情况下,不需要随机读取另一个页中的记录,因此,这类插入是非常快速的。 但如果主键是指定的值,即使是自增长的,也可能导致插入并非连续的情况。 多数情况下,表中除了主键索引外,还存在其他非聚集索引。 比如如下定义SQL:
用户需按照b字段进行查找,并且b字段不是唯一的。这样就产生了非聚集且不唯一的索引。在进行插入操作时,数据页的存放还是按照主键a的顺序进行存放的。但是对于非聚集索引叶子节点的插入不再是顺序的。这时需要离散地访问非聚集索引页,由于随机读取的存在而导致插入操作性能下降。 InnoDB设计Insert Buffer,对于非聚集索引的插入或更新操作,不是每一次直接插入到索引页中,而是先判断插入的非聚集索引是否在缓冲池中,若在,则直接插入;若不在,则先放入一个Insert Buffer对象中。然后以一定的频率和情况进行Insert Buffer和辅助索引页子节点的merge操作。这时通常可以将多个操作插入操作合并到一个操作中,因此提供了非聚集索引的插入性能。 Insert Buffer使用需要满足以下两个条件:
如果辅助索引是唯一的,就不能使用该技术,原因很简单,因为如果这样做,整个索引数据被切分为2部分,无法保证唯一性。 插入缓冲主要带来如下两个坏处:
Change Buffer
Insert Buffer实现
Insert Buffer是一颗B+树。全局只有一个Insert Buffer负责对所有表的辅助索引进行Insert Buffer。这颗B+树存放在共享表空间中(ibdata1)中。 (未完待续)
两次写
保证数据页的可靠性。 部分写失效:当InnoDB在写入数据页到表时,在写入过程中,发生宕机的情况。 重做日志记录的是对页的物理表操作,如果数据页本身发生损坏,在对其进行重做是无意义的。 因此,在应用重做日志之前,需要一个页的副本,当写入失效发生时,先通过页的副本还原该页,再进行重做。这就是两次写。 两次写需要额外添加两个部分:
这样就可以解决上文提到的部分写失效的问题,因为在磁盘共享表空间中已有数据页副本拷贝,如果数据库在页写入数据文件的过程中宕机,在实例恢复时,可以从共享表空间中找到该页副本,将其拷贝覆盖原有的数据页,再应用重做日志即可。 其中第2步是额外的性能开销,但由于磁盘共享表空间是连续的顺序写,因此开销不是很大。
自适应哈希索引
哈希索引是一种非常快的等值查找方法(注意:必须是等值,哈希索引对非等值查找方法无能为力),它查找的时间复杂度为常量,InnoDB采用自适用哈希索引技术,它会实时监控表上索引的使用情况,如果认为建立哈希索引可以提高查询效率,则自动在内存中的“自适应哈希索引缓冲区”建立哈希索引。
异步IO
为了提高磁盘操作性能,InnoDB存储引擎采用异步IO(Asynchronous IO,AIO)的方式来处理磁盘操作。
IO Merge
AIO另一个优势是进行IO Merge操作,也就是将多个IO合并为1个IO,这样可以提高IOPS的性能。例如用户需要访问页的(space, offset)为: (8,6),(8,7),(8,8) 每个页的大小为16KB,那么同步IO需要进行3次IO操作。而AIO会判断到这三个页是连续的(可以通过(space,offset)知道)。因此AIO底层会发送一个IO请求,从(8,6)开始,读取48KB的页。
刷新邻接页
InnoDB存储引擎还提供了Flush Neighbor Page(刷新邻接页)的特性。其工作原理为:当刷新一个脏页时,InnoDB存储引擎会检测该页所在区(extent)的所有页,如果是脏页,那么一起进行刷新。 有两个问题:
启动关闭与恢复
在关闭时,参数 innodb_fast_shutdown影响着表的InnoDB存储引擎的行为,该参数可以为0,1,2,默认值为1;
当正常关闭MySql数据库时,下次的启动应该会非常正常。但是如果没有正常地关闭数据库,如用kill命令关闭数据库,在MySql数据库运行中重启了服务器,或者在关闭数据库时,将参数innodb_fast_shutdown设置为了2,下次MySql数据库启动时都会对InnoDB存储引擎的表进行恢复操作。 参数innodb_force_recovery影响了整个InnoDB存储引擎恢复的状态。该参数默认值为0,表示当发生需要恢复时,进行所有的恢复操作,当不能进行有效恢复时,如数据页发生了corruption,MySql数据库可能发生宕机(crash),并把错误写入错误日志中去。 某些情况下,可能并不需要进行完整的恢复操作,因为用户自己知道怎么进行恢复。比如在对一个表进行alter table操作时发生了意外,数据库重启时会对InnoDB表进行回滚操作,对于一个大表来说这需要很长时间,可能是几个小时。这时用户可以自行进行恢复,如可以把表删除,从备份中重新导入数据到表,可能这些操作的速度要远远快于回滚操作。
参数innodb_force_recovery还可以设置为6个非零值:1-6,大的数字表示包含了前面所有小数字表示的影响。具体情况如下: