返回 登录
0

网易视频云:INNODB insert buffer 简单分析

阅读1959

在mysql5.1 之前称为Insert Buffer, 优化2级非唯一索引上插入操作的读IO, 在5.5之后改名为Change Buffer, 功能也扩展为2级非唯一索引上的插入、删除、更新、purge的读IO优化。
change buffer的核心思想,当数据库需要对2级缓存进行修改时,先不从外存读页面,而是将这些更新缓存在内存中,在特定的条件下,统一将这些更新apply到相应的2级索引页面上,这样做可以减少读IO的次数,并且相邻的页面的读IO可以合并。
在源码中的命名一直还是用ibuf,因此之后都用ibuf来指代InsertBuffer

Insert Buffer的信息可以在show innodb status中看到,例如:

INSERT BUFFER AND ADAPTIVE HASH INDEX

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缩小操作

  1. 根据操作类型,索引,索引记录,spaceid, 页面号, 计数(初始化为0xffff)等构建ibuf记录,ibuf索引记录的field依次为(1)spaceid (4字节), (2)marker byte此处初始化为0 (1字节), (3)page number (4字节),(4)type info (4字节,前两字节标示时同属一个索引页面的记录计数,第三字节标示是何种操作 插入/删除/del by mark,第4字节是标示记录格式), (5)之后才是索引记录的各个属性

  2. 如果是修改整棵树,那么需要加上ibuf悲观插入mutex, 再加ibuf_mutex,检查是否ibuf有足够空间来进行插入操作,如果没有的话,从ibuf文件的segment中分配一个页面放到freelist中

4.启动微事务mtr,此处将mtr的inside_ibuf参数设置为true

  1. 根据tuple去b+树中做search path, 寻找在同一个索引页面上已经缓存着的插入操作,统计大小(根据ptr 想前扫到第一条属于同一个page_no的页面,然后再向后扫,不精确,考虑到跨多个ibuf页面,因此是一个upperBound)

  2. 如果是删除操作,那么当删除记录后页面为空,那么就不需要缓存这些操作

  3. 新起一个bitmap的微事务,

  4. 检查是否index page是否合适buffered,,(如果能在buffer中的hash表中找到该页面,或者页面上有显式记录锁,则返回失败)

  5. 根据bitmap上的页面空间信息 ibuf_index_page_calc_free_from_bits,来计算merge上去的数据会不会导致索引页面分裂。如果会分裂的话,搜集一堆会分裂的页面,将那些索引页面读到buffer中,do_merge标志位设为true,ibuf插入返回失败

  6. 根据spaceid 和 page_no 统计ibuf中有几条记录,更新数据上的count计数

  7. 修改ibuf的bitmap上的信息(显示bool值,该页面上含有记录更新的缓存)

  8. 提交bitmap的微事务

  9. 乐观地去ibuf b+树上插入数据(认为不会分裂),如果此时页面上只有一条记录,默认插入一定会成功,这是为了防止一条记录的页面分裂

14,如果是单页面修改的模式,如果是根页面,那么要更新ibuf是否为空的标志位;如果是整棵树的修改模式,那么做悲观插入(微事务需要持有cur所在页面包括兄弟页面的x latch)然后放掉ibuf的ibuf_pessimistic_insert_mutex, 更新ibuf的页面统计信息。

  1. 如果插入成功,并且不是IBUF_OP_DELETE操作的话,更新ibuf页面上的max_trx_id.

  2. 提交ibuf微事务

  3. 插入成功后,如果ibuf过大,需要做contract

  4. 如果过程中设置了do_merge位,则做merge操作。

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保持页面的一致性

  1. 一堆页面检测

  2. 开启微事务获取butmap页面和bits

  3. 构造一个ibuf tuple, 去搜索某个索引页面相关的所有ibuf缓存操作,ibuf_search_tuple_build

  4. 开始一个ibuf微事务

  5. 根据pcurs指针遍历ibuf所有同一索引页面的操作记录,用ibuf页面上的max_trxid来更新索引页面上的max_max_trxId

  6. 从ibuf tuple中提取出索引项entry

  7. 根据不同的操作类型,做 insert_to_index_page 操作, 或者是 set_del_mark操作,或者是 delete操作

  8. 删除一条对应的ibuf记录

  9. 设置对应的ibuf bitmap

总的来说,在系统尝试使用insert buffer失败(条件不满足时)会真正去从外存读取索引页面,也就自然触发了merge操作。

评论