在mysql5.1 之前称为Insert Buffer, 优化2级非唯一索引上插入操作的读IO, 在5.5之后改名为Change Buffer, 功能也扩展为2级非唯一索引上的插入、删除、更新、purge的读IO优化。
change buffer的核心思想,当数据库需要对2级缓存进行修改时,先不从外存读页面,而是将这些更新缓存在内存中,在特定的条件下,统一将这些更新apply到相应的2级索引页面上,这样做可以减少读IO的次数,并且相邻的页面的读IO可以合并。
在源码中的命名一直还是用ibuf,因此之后都用ibuf来指代InsertBuffer
Ibuf: size 7545, free list len 3790, seg size 11336 (单位是page)
8075308 inserts, 7540969 merged recs, 2246304 merges
};
从上述的结构体可以得知,Ibuf实际上也是一棵B+树索引,它与innodb中其他的b+树有着完全一样的结构。Ibuf树中的记录其实就是包含了记录本身,还有记录所在页面号的信息。具体下面会分析。
Ibuf本身和double wirte buffer 一样属于系统表空间,因此也会物化,特别是在崩溃恢复时也需要考虑在内。
Ibuf bitmap用来记录二级非唯一索引中页面的空闲空间的。当插入/更新会引发索引树SMO时,Ibuf不可用,这是因为如若发生SMO,ibuf树中记录的页面信息会部分失效,而具体这些失效页面会最终落,在哪个页面上是未知的。因此每次对bitMap的判断是每次ibuf插入修改时必不可少的步骤。(代码注释ulint bit_offset : 根据page_no和bit计算得来,高5位是 byte_offset, 低3位是bit_offset),Ibuf bitmap也是由一系列的页面构成,每一个Ibuf Bitmap页面记录了一堆索引页面的页面空闲空间状况
插入操作:
实验方法:
create table t1(id int primary key auto_increment, name varchar(2000) index idx1 name)engine=innodb;
为了防止buffer中缓存二级索引页面,因此需要事先导入大量数据。 利用inser into select 语句导入65536条数据,保证次级索引树超过3层。
插入操作在btr_cur_search_to_nth_level方法中会有涉及到ibuf 的操作,索引操作在搜索路径时,有一把整棵B+树的大锁。在索引search path 方法中去调buf_get_gen 时如若缓存未命中,并且是次级非唯一索引,则触发insert buffer的发动条件
ibuf_should_try函数中剔除cluster索引和unique索引(但是可以指定忽略次级索引的unique特性)
root页面以及非叶页面不会用到insert buffer(根页面常驻内存)
如果latchMode<=BTR_MODIFY_LEAF 即不会发生smo,才会使用insert buffer,这里要注意的是innodb会先尝试以乐观的BTR_MODIFY_LEAF的方式进行,失败了再调用悲观的BTR_MODIFY_TREE去锁整棵树
当buf_get_gen 返回的block 为NULL时,进入ibuf_insert方法
ibuf_insert 流程:
1. 通过buf_page_hash_get_low 检测插入的页面是否未在缓存中命中
2. 检测tuple size 是否过大(大于一个空页面的freespace的 1/2)
3. 乐观进行ibuf_insert_low(BTR_MODIFY_PREV, 不修改整棵树)
4. 如若3失败,则悲观进行ibuf_insert_low(BTR_MODIFY_TREE,修改整棵树)
ibuf_insert_low操作的流程:
1. 脏读ibuf->size, 判断是否需要做ibuf 的contract缩小操作
4.启动微事务mtr,此处将mtr的inside_ibuf参数设置为true
14,如果是单页面修改的模式,如果是根页面,那么要更新ibuf是否为空的标志位;如果是整棵树的修改模式,那么做悲观插入(微事务需要持有cur所在页面包括兄弟页面的x latch)然后放掉ibuf的ibuf_pessimistic_insert_mutex, 更新ibuf的页面统计信息。
Ibuf 的 merge pages 操作:
merge操作会有多种情况触发,一种是innodb master线程主动触发,在数据库关闭时根据不同的参数也会merge ibuf。另一种是当有索引页面从外存读到内存,在使用前必须将ibuf中缓存的内容merge过去。
master线程当系统io空闲时会去merge,然后每10s也会做一次,merge 系统IO能力页面数的5%。 系统IO能力默认为200个页面美妙。
Innodb_fast_shutdown告诉innodb在它关闭的时候该做什么工作。有三个值可以选择:
当选择0时表示在innodb关闭的时候,需要purge all, merge insert buffer,flush dirty pages。这是最慢的一种关闭方式,但是restart的时候也是最快的。
merge需要拿着整个ibuf的mutex来做?不需要,因为会先读取次级索引页面并加次级索引页面的latch,因此
(ibuf_merge_or_delete_for_page,这个方法是merge到索引页面,并且删除ibuf中记录)
Ibuf 的contract操作(merge,并且清除ibuf中的数据):
ibuf_contract_for_n_page为入口函数,
ibuf的b+树调用btr_pcur_open_at_rnd_pos 随机在ibuf选择游标来达到随机选取页面的目的。
之后innodb也时将选中的记录涉及到的页面从外存读取到内存,从而真正触发merge操作
当页面从外存load到buffer中,会在buf_page_io_complete中调用ibuf_merge_or_delete_for_page保持页面的一致性
总的来说,在系统尝试使用insert buffer失败(条件不满足时)会真正去从外存读取索引页面,也就自然触发了merge操作