InnoDB存储引擎的关键特性包括:
- 插入缓冲(Insert Buffer)
- 两次写(double Write)
- 自适应哈希索引(Adaptive Hash Index)
- 异步IO(Async IO)
- 刷新邻接页(Flush Neighbor Page)
插入缓冲
1. Insert Buffer
Insert Buffer是InnoDB存储引擎关键特性中最令人激动与兴奋的一个功能。不过这个名字可能会让人认为插入缓冲是缓冲池中的一个组成部分。其实不然,InnoDB缓冲池中有Insert Buffer信息固然不错,但是Insert Buffer和数据页一样,也是物理页的一个组成部分。
一般情况下,主键是行唯一的标识符。通常应用程序中行记录的插入顺序是按照主键递增的顺序进行插入的。因此,插入聚集索引一般是顺序的,不需要磁盘的随机读取。因为,对于此类情况下的插入,速度还是非常快的。
注:如果主键类是UUID这样的类,那么插入和辅助索引一样,也是随机的。
如果索引是非聚集的且不唯一。在进行插入操作时,数据的存放对于非聚集索引叶子节点的插入不是顺序的,这时需要离散地访问非聚集索引页,由于随机读取的存在而导致了插入操作性能下降。这是因为B+树的特性决定了非聚集索引插入的离散性。
InnoDB引擎开创性地设计了Insert Buffer,对于非聚集索引的插入和更新操作,不是每一次直接插入到索引页中,而是先判断插入非聚集索引页是否在缓冲池中,若存在,则直接插入,不存在,则先放入一个Insert Buffer对象中。数据库这个非聚集的索引已经插到叶子节点,而实际并没有,只是存放在另一个位置。然后再以一定的频率和情况进行Insert Buffer和辅助索引页子节点的merge(合并)操作,这时通常能将多个插入合并到一个操作中(因为在一个索引页中),这就大大提高了对于非聚集索引插入的性能。
Insert Buffer的使用需要同时满足两个条件:
- 索引是辅助索引;
- 索引不是唯一的。
辅助索引不能是唯一的,因为在插入缓冲时,数据库并不去查找索引页来判断插入的记录的唯一性。如果去查找肯定又会有离散读取的情况发生,从而导致Insert Buffer失去了意义。
Insert Buffer目前存在的问题:
- 在写密集的情况下,插入缓存会占用过多的缓冲池内存(innodb_buffer_pool),默认最大可以占用到1/2的缓冲池内存。这对于其他操作可能会带来一定的影响。
- 可能导致数据库宕机后实例恢复时间变长。如果应用程序执行大量的插入和更新操作,且涉及非唯一的聚集索引,一旦出现宕机,这时就有大量内存中的插入缓冲区数据没有合并至索引页中,导致实例恢复时间会很长 。
2. Change Buffer
InnoDB从1.0.x版本开始引入了Change Buffer,可将其视为Insert Buffer的升级。从这个版本开始,InnoDB存储引擎可以对DML操作——INSERT、DELETE、UPDATE都进行缓冲,他们分别是:Insert Buffer、Delete Buffer、Purge buffer。
当然和之前Insert Buffer一样,Change Buffer适用的对象依然是非唯一的辅助索引。
对一条记录进行UPDATE操作可能分为两个过程:
- 将记录标记为已删除(Delete Buffer);
- 真正将记录删除(Purge buffer)。
因此Delete Buffer对应UPDATE操作的第一个过程,即将记录标记为删除。Purge Buffer对应UPDATE操作的第二个过程,即将记录真正的删除。同时,InnoDB存储引擎提供了参数innodb_change_buffering,用来开启各种Buffer的选项。该参数可选的值为:inserts、deletes、purges、changes、all、none。inserts、deletes、purges就是前面讨论过的三种情况。changes表示启用inserts和deletes,all表示启用所有,none表示都不启用。该参数默认值为all。
从InnoDB 1.2.x版本开始,可以通过参数innodb_change_buffer_max_size来控制Change Buffer最大使用内存的数量,默认值为25,表示最多使用1/4的缓冲池内存空间。而需要注意的是,该参数的最大有效值为50。
在MySQL 5.5版本中通过命令SHOW ENGINE INNODB STATUS,可以观察到类似如下的内容:
mysql> SHOW ENGINE INNODB STATUS\G;
……
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 34397, seg size 34399, 10875 merges
merged operations:
insert 20462, delete mark 20158, delete 4215
discarded operations:
insert 0, delete mark 0, delete 0
……
可以看到这里显示了merged operations和discarded operation,并且下面具体显示Change Buffer中每个操作的次数。insert表示Insert Buffer;delete mark表示Delete Buffer;delete表示Purge Buffer;discarded operations表示当Change Buffer发生merge时,表已经被删除,此时就无需再将记录合并(merge)到辅助索引中了。
3. Insert Buffer 的内部实现
前一小节已经知道Insert Buffer的使用场景,即非唯一辅助索引的插入操作。但对于Insert Buffer具体是什么,以及内部怎么实现的?正是本节所要阐述的内容。
Insert Buffer的数据结构是一棵B+树,在MySQL4.1之前的版本中每张表都有一棵Insert Buffer B+树
MySQL4.1之后,全局只有一棵insert buffer B+树,负责对所有的表的辅助索引进行 Insert Buffer 。这棵B+树存放在共享表空间中,默认也就是ibdata1中。因此,试图通过独立表空间ibd文件恢复表中数据时,往往会导致check table 失败。这是因为表的辅助索引中的数据可能还在Insert Buffer中,也就是共享表空间中。所以通过idb文件进行恢复后,还需要进行repair table 操作来重建表上所有的辅助索引。
Insert Buffer 是一颗B+树,因此它是由叶节点和非叶节点组成。非叶子节点存放的是查询的search key(键值)。 构造如图:
其构造包括三个字段:space (4 byte)+ marker(1byte) + offset(4byte) = search key (9 byte )space表示待插入记录所在的表空间id,InnoDB中,每个表有一个唯一的space id,可以通过space id查询得知是哪张表;marker是用来兼容老版本的insert buffer;offset表示页所在的偏移量。
当一个辅助索引需要插入到页(space, offset)时,如果这个页不在缓冲池中,那么InnoDB首先根据上述规则构造一个search key,接下来查询Insert Buffer这棵B+树,然后再将这条记录插入到Insert Buffer B+树的叶子节点中 。
对于插入到Insert Buffer B+树叶子节点的记录,需要根据如下规则进行构造:
space | marker | offset | metadata | secondary index record
启用Insert Buffer索引后,辅助索引页(space、page_no)中的记录可能被插入到insert buffer B+树中,所以为了保证每次merge insert buffer页必须成功,还需要有一个特殊的页来标记每个辅助索引页(space、page_no)的可用空间,这个页的类型为Insert Buffer Bitmap。
4. Merge Insert Buffer
通过前面的小节应该已经知道了Insert/Change Buffer是一棵B+树。若需要实现插入记录的辅助索引页不在缓冲池中,那么需要将辅助索引记录插入到这棵实际B+树中。但是Insert Buffer中的记录何时合并(merge)到真正的辅助索引中呢?
概括地说,Merge Insert Buffer的操作可能发生在以下几种情况下:
- 辅助索引页被读取到缓冲池时;
- Insert Buffer Bitmap页追踪到该辅助索引页已无可用空间时;
- Master Thread。
第一种情况为当辅助索引页被读取到缓冲池中时,例如这在执行正常的SELECT查询操作,这时需要检查Insert Buffer Bitmap页,然后确认该辅助索引页是否有记录存放于Insert Buffer B+树中。若有,则将Insert Buffer B+树中该页的记录插入到该辅助索引页中。可以看到对该页多次的记录操作通过一次操作合并到了原有的辅助索引页中,因此性能会有大幅提高。
第二种情况为 Insert Buffer Bitmap页用来追踪每个辅助索引页的可用空间,并至少有1/32页的空间。若插入辅助索引记录时检测到插入记录后可用空间会小于1/32页,则会强制进行一个合并操作,即强制读取辅助索引页,将Insert Buffer B+树中该页的记录及待插入的记录插入到辅助索引页中。
还有一种情况,之前在分析Master Thread时曾讲到,在Master Thread线程中每秒或每10秒会进行一次Merge Insert Buffer的操作,不同之处在于每次进行merge操作的页的数量不同。