InnoDB 关键特性
InnoDB存储引擎的关键特性包括:
- Insert Buffer (插入缓冲)
- Double Write (两次写)
- Adaptive Hash Index (自适应哈希索引)
- Async IO (异步IO)
- Flush Neighbor Page (刷新领接页)
这些特性为InnoDB存储引擎带来了更好的性能以及更高的可靠性。
插入缓冲
1. Insert Buffer
Insert Buffer可能是InnoDB存储引擎关键特性中最令人激动与兴奋的一个功能。不过这个名字可能会让人认为插入缓冲是缓冲池中的一个组成部分。其实不然,InnoDB缓冲池中有Insert Buffer信息固然不错,但是Insert Buffer和数据页一样,也是物理页的一个组成部分。
在InnoDB存储引擎中,主键是行唯一的标识符。通常应用程序中行记录的哈如顺序是按照主键递增的顺序进行插入的。因此,插入聚集索引(Primary Key)一般是顺序的,不需要磁盘的随机读取。比如按下列SQL定义表:
create table zxy (
a int auto_increment,
b varchar(30),
primary key(a)
);
其中a列是自增长的,若对a列插入null值,则由于其具有auto_increment属性,其值会自动增长。同时页中的行记录按a的值进行顺序存放。在一般情况下,不需要随机读取另一个页中的记录。因此对于这类情况下的插入操作,速度是非常快的。
注意:并不是所有的主键插入都是有顺序的。若主键类是UUID这样的类,那么插入和辅助索引一样,同样是随机的。即使主键是自增类型,但是插入的是指定的指,而不是NULL指,那么同样可能导致插入并非连续的情况。
但是不可能每张表上只有一个聚集索引,更多情况下,一张表上有多个非聚集的辅助索引(secondary index)。比如,用户需要按照b这个字段进行查找,并且b这个字段不是唯一的,即表是按如下的SQL语句定义的:
create table zxy (
a int auto_increment,
b varchar(30),
primary key(a),
key(b)
);
在这样的情况下产生了一个非聚集的且不是唯一的索引。在进行插入操作时,数据页的存放还是按主键a进行顺序存放的,但是对于非聚集索引叶子节点的插入不再是顺序的了,这时就需要离散的访问非聚集索引页,由于随机读取的存在而导致了插入操作性能下降。当然这并不是b字段上索引的错误,而是因为B+树的特性决定了非聚集索引插入的离散型。
需要注意的是,在某些情况下,辅助索引的插入依然是顺序的,或者说是比较顺序的,比如用户购买表中的事件字段。在通常情况下,用户购买时间是一个辅助索引,用来根据时间条件进行查询。但是在插入时却也是根据时间的递增而插入的,因此插入也是“较为”顺序的。
InnoDB存储引擎开创性的设计了Insert Buffer,对于非聚集索引的插入或更新操作,不是每次直接插入到索引页中,而是先判断插入的非聚集索引页是否在缓冲池中,若在,则直接插入;若不在,则先放入到一个Insert Buffer对象中,好似欺骗。数据库这一个非聚集索引已经插入到叶子节点,而实际并没有,只是存放在另一个位置。然后再以一定的频率和情况进行Insert Buffer和辅助索引页子节点的merge(合并)操作,这时通常能将多个插入合并到一个操作中(因为在一个索引页中),这样就大大的提高了对于非聚集索引插入的性能。
然而Insert Buffer的使用需要满足以下两个条件:
- 索引是辅助索引 (secondary index)
- 索引不是唯一的 (unique)
当满足以上两个条件时,InnoDB存储引擎会使用Insert Buffer,这样就能提高插入操作的性能了。不过考虑到这样一种情况:应用程序进行大量的插入操作,这些都涉及了不唯一的非聚集索引,也就是使用Insert Buffer的情况。若此时MySQL数据库发生了宕机,这时势必有大量的Insert Buffer并没有合并到实际的非聚集索引中。因此这时恢复可能需要很长时间,在极端情况下,甚至需要几个销售。
辅助索引不能是唯一的
,因为在插入缓冲时,数据库并不去查找索引页来判断插入的记录的唯一性。如果去查找肯定会有离散读取的情况发生,从而导致Insert Buffer失去了意义。
而Insert Buffer还存在的一个问题是:在写密集的情况下,插入缓冲会占用过多的缓冲池内存(innodb_buffer_pool),默认最大可以占用到1/2
的缓冲池内存。但是可以通过ibuf_pool_size_per_max_size
对插入缓冲大小进行控制。当ibuf_pool_size_per_max_size
改为3时,则最大只能占用1/3
的缓冲池内存。
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对应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最大使用内存的数量。
mysql> show variables like 'innodb_change_buffer_max_size'\G;
*************************** 1. row ***************************
Variable_name: innodb_change_buffer_max_size
Value: 25
1 row in set (0.00 sec)
innodb_change_buffer_max_size
值默认为25,表示最多使用1/4的缓冲池内存空间。需要注意的是,该参数最大有效值是50,也就是最多使用1/2的缓冲池内存空间。
通过show engine innodb status;
可以看到merged operations和discard operation
,并且下面显示Change Buffer中每个操作的次数。insert表示insert buffer;delete mark表示delete buffer;delete表示purge buffer;discard operations表示当change buffer发生了merge时,表已经被删除了,此时无需再将记录合并(merge)到辅助索引中。
mysql> show engine innodb status\G;
......
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 0, seg size 2, 0 merges
merged operations:
insert 0, delete mark 0, delete 0
discarded operations:
insert 0, delete mark 0, delete 0
......
3. Insert Buffer的内部实现
通过前面的介绍可以知道Insert Buffer的使用场景是非唯一辅助索引的插入操作。但是对于Insert Buffer具体是什么,以及内部怎么实现还处于模糊阶段。
其实Insert Buffer的数据结构是一颗B+树。在MySQL 4.1之前的版本中每张表有一颗Insert Buffer B+树。而在现在的版本中,全局只有一颗Insert Buffer B+树,负责对所有表的辅助索引进行Insert Buffer。而这颗B+树存放在共享表空间中,默认也就是idbatal中。因此,视图沟通独立表空间idb文件恢复表中数据时,往往会导致CHECK TABLE失败。这时因为表的辅助索引中数据肯恶搞还在Insert Buffer中,也就是共享表空间中,所以通过idb文件进行恢复后,还需要进行REPLACE TABLE操作来重建表上所有的辅助索引。
Insert Buffer是一颗B+树,因此其也由叶子节点和非叶子节点组成。非叶子节点存放的是查询的search key(键值),其构造如下所示:
space | marker | offset
search key 一共占用9个字节,其中space表示待插入记录所在表空间id,在InnoDB存储引擎中,每个表有一个唯一的space id,可以通过sapce id查询得知是哪张表。space占用4字节。marker占用1字节,它是用来兼容老版本的Insert Buffer。offset表示页所在的偏移量,占用4字节。
当一个辅助索引要插入到页(space,offset)时,如果这个页不在缓冲池中,那么InnoDB存储引擎首先根据上述规则构造一个search key,接下来查询insert buffer这颗B+树,然后再将这条记录插入到Insert Buffer B+树的叶子节点中。
对于插入到Insert Buffer B+树叶子节点的记录,并不是直接将待插入的记录插入,而是需要根据如下的规则进行构造:
space、marker、page_no字段和之前非叶子节点中含义相同,一共占用9字节。第4个字段metadata占用4字节,其存储的内容如下:
IBUF_REC_OFFSET_COUNT
是保存两个字节的帧数,用来排序每个记录进入Insert Buffer的顺序。因为从InnoDB 1.0.x开始支持Change Buffer,所以这个值同样记录进入Insert Buffer的顺序。通过这个顺序回放(replay)才能得到记录的正确值。
名称 | 字节 |
---|---|
IBUF_REC_OFFSET_COUNT | 2 |
IBUF_REC_OFFSET_TYPE | 1 |
IBUF_REC_OFFSET_FLAGES | 1 |
从叶子节点的第5列开始,就是实际插入记录的各个字段。因此相较于之前的插入记录,Insert Buffer B+树的叶子节点记录需要额外13字节的开销。
因为启用Insert Buffer索引后,辅助索引页(space,page_no)中的记录可能被插入到Insert Buffer B+树中,所以为了保证每次Merge Insert Buffer页必须成功,还需要有一个特殊的页来标记每个辅助索引页(space,page_no)的可用空间。这个页的类型为Insert Buffer Bitmap。
每个Insert Buffer Bitmap页用来追踪16384个辅助索引页,也就是256个区(Extent)。每个Insert Buffer Bitmap页都在16384个页的第二个页中。
每个辅助索引页在Insert Buffer Bitmap页中占用4位(bit),由如下三个部分组成:
名称 | 大小(bit) | 说明 |
---|---|---|
IBUF_BITMAP_FREE | 2 | 表示该辅助索引的可用空间数量 0 表示无可用剩余空间 1 表示剩余空间大于1/32页(512字节) 2 表示剩余空间大于1/16页 3 表示剩余空间大于1/8页 |
IBUF_BITMAP_BUFFERED | 1 | 1 表示该辅助索引页有记录被缓存在Insert Buffer B+树中 |
IBUF_BITMAP_IBUF | 1 | 1 表示该页为Insert Buffer B+树的索引页 |
4. Merge Insert Buffer
通过前面的介绍可知,Insert/Change Buffer
是一颗B+树。若需要实现插入记录的辅助索引不在缓冲池中,那么需要将辅助索引记录首先插入到这颗B+树中。但是Insert Buffer中的记录何时合并(merge)到真正的辅助索引中呢?
概况的说,Merge Insert Buffer的操作可能发生以下几种情况:
- 辅助索引页被读取到缓冲池时
- Insert Buffer Bitmap页追踪到该辅助索引页时已经无可用空间
- Master Thread
第一种情况为当辅助索引页被读取到缓冲池中时,例如在这个执行正常的select查询操作,这时需要检查Insert Buffer Bitmap
页,然后u企鹅人该辅助索引页是否有记录存放到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操作页数量不同。
在Master Thread中,执行merge操作的不止是一个页,而是根据srv_innodb_io_capacity
的百分比来决定真正要合并多少个辅助索引页。但InnoDB存储引擎又是根据怎样的算法来得知需要合并的辅助索引页呢?
在Insert Buffer B+
树种,辅助索引页根据(space,offset)都已排序好,所以可以根据(sapce,offset)的排序顺序进行页的选择。然而,对于Insert Buffer页的选择,InnoDB存储引擎并非采用这个方式,它随机的选择Insert Buffer B+
树的一个页,读取该页种sapce及之后所需要数量的页。该算法在复杂的情况下应有更好的公平性。同时,若进行merge时,要进行merge的表已经被删除,此时可以直接丢弃已经被Insert Change Buffer
的数据记录。