参考
http://mysql.taobao.org/monthly/2015/07/01/
- ibuf说明
ibuf本质上是一个B+树,主要存储在ibdata中,其最大大小不能超过参数innodb_change_buffer_max_size的设置,如果超过则会触发合并操作,而在各个tablespace中的ibuf map page 主要存储的是page空闲空间的位图,主要作用是用来防止ibuf合并导致二级索引的B+树变化,因为ibuf的记录使用space和page no来定位需要合并的page,如果page no发生变化则不能合并,因此在写入ibuf内容的时候需要判断缓存的操作是否对二级索引B+树带来变化。
- 不能进行IBUFF缓存的情况
A:如果本次是删除行,也就是delete操作(purge线程),并且二级索引中只有1条数据了或者二级索引page已经再读取中,这可能导致二级索引页合并,不能缓存
B:如果本次是插入行,也就是是insert操作,二级索引没有空闲,可能导致二级索引页分裂不能缓存
C:如果缓存大小已经大于了参数设置的大小,则不能缓存,且触发8个page的合并操作
D:参数关闭了对应操作的change buffer则不进行缓存
E:page已经在innodb buffer中不需要缓存
F:本次操作的行大于了二级索引页的1/2不进行缓存,这个基本不会出现
G:主键不缓存,二级唯一索引只缓存删除操作,因为如果缓存则可能导致唯一性不满足的情况
- 参数修改innodb_change_buffering
参数的修改不会影响现有的ibuf信息,但是设置后就不会进行ibuf的写入,因此现有的ibuf也会慢慢合并掉。包含inserts/deletes/changes/purges/none 5个选项
- 修改参数innodb_change_buffer_max_size
可能导致更多的合并操作,导致IO上升,ibuf_max_size_update(srv_change_buffer_max_size)
因为在ibuf insert中会判断是否超过了ibuf缓存的最大值,如果大于则会触发随机合并操作(IBUF_MERGE_AREA 8个pages)
- IBUF和二级索引的关系
因为二级索引的修改都会涉及主键到二级索引的定位操作,因为先修改主键然后,修改二级索引,因此主要在btr_cur_search_to_nth_level定位函数上判断是否可以进行ibuf缓存
row_upd_sec_index_entry
...
->btr_cur_search_to_nth_level
- 缓存的操作主要有
IBUF_OP_INSERT = 0 insert操作
IBUF_OP_DELETE_MARK = 1 delete操作
IBUF_OP_DELETE = 2 purge线程操作
对于二级索引update都是delete和insert操作,但是delete mark的行可以在下一次二级索引插入同样长度行的时候进行重用
- ibuf记录的结构
1、此处完成后ibuf中的字段包含4+用户字段数量如下
A、space id 4 IBUF_REC_FIELD_SPACE
B、marker 1 IBUF_REC_FIELD_MARKER
C、page no 4 IBUF_REC_FIELD_PAGE
D、metadata 可变
IBUF_REC_FIELD_METADATA
IBUF_REC_OFFSET_COUNTER 2 -
IBUF_REC_OFFSET_TYPE 1 - --> BUF_REC_INFO_SIZE 4
IBUF_REC_OFFSET_FLAGS 1 -
n*DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE 可变 n为字段
每个DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE
字段类型 2
字段长度 2
字符集类型 2
E、user field 可变
用户字段
- 引入sentinel page的原因
update 修改二级索引 是 delete 和 insert这个时候delete 记录由于read view需要不能清理 一直保留着,这个时候二级索引 insert 一行相同的或者长度相同的二级索引记录,那这个del flag记录可能重用。
row_ins_must_modify_rec:用于主键和二级索引来判断是否直接利用del flag的记录。
row_ins_sec_index_entry_by_modify:考虑进行原地更新,二级索引主要出现在del flag标记的二级索引上。因为二级索引又是ibuf 缓存重要对象,因此purge 线程在对ibuf做del flag标记记录清理的时候会特别注意,因为可能这个del flag记录会被重用,这个时候需要引入ibuffer instance 的watch数组。
- 初始化sentinel page,每个instance 都有 purge 线程+1个
buf_pool_init_instance
->buf_pool->watch = (buf_page_t*) ut_zalloc_nokey( sizeof(*buf_pool->watch) * BUF_POOL_WATCH_SIZE)
每个instance 初始化 purge + 1 个 watch pages
->for (i = 0; i < BUF_POOL_WATCH_SIZE; i++)
buf_pool->watch[i].buf_pool_index = buf_pool->instance_no;
初始化每个watch page 的index 为本buf pool的instance_no
- buf_pool_watch_set
主要是再purge线程获取page的时候设置page为BUF_BLOCK_ZIP_PAGE,防止二级索引的记录和前台session并发处理。初始化page的状态为BUF_BLOCK_POOL_WATCH ,也就是sentinel page处于空闲状态,purge持有的情况下转变为BUF_BLOCK_ZIP_PAGE状态
buf_page_get_gen
->if (mode == BUF_GET_IF_IN_POOL_OR_WATCH)
如果获取page的时候是需要这个状态,则是purge线程
->buf_pool_watch_set
先要寻找page是否再page中,然后判断page是否已经设置为BUF_BLOCK_ZIP_PAGE
如果是则不做处理了
首先获取一个buffer pool上的sentinel page
for (i = 0; i < BUF_POOL_WATCH_SIZE; i++)
->switch (bpage->state)
case BUF_BLOCK_POOL_WATCH:
->bpage->state = BUF_BLOCK_ZIP_PAGE
page更改为BUF_BLOCK_ZIP_PAGE状态
->bpage->id.copy_from(page_id)
->bpage->buf_fix_count = 1
->HASH_INSERT
将这个page的信息放入sentinel page中
buf_pool_watch_unset
是上面函数的逆过程,主要调用是再purge线程缓存完ibuf操作后设置
也就是函数btr_cur_search_to_nth_level中BTR_DELETE_OP操作调用buf_pool_watch_remove
buf_pool_watch_unset 也会调用这个函数,物理读取初始化page的时候更改sentinel page为BUF_BLOCK_POOL_WATCH,并且清理掉buffer pool hash中的缓存
- buf_pool_watch_is_sentinel
这个函数用于判断是否是sentinel page其调用者主要包含buf_page_get_gen,如果是sentinel page则直接进入物理读取流程
- ibuf_merge_or_delete_for_page
这个函数是应用ibuf到block的主要函数,主要的合并是在IO读取完成的时候进行也就是io_complete 中
- 合并时机
1、IO 读取完成后
2、ibuf空间不够,随机合并8个page
3、master 线程每秒合并
4、slow shutdown
5、crash recovery
6、flush table for export (row_quiesce_table_start)
主要工作是对于
IBUF_OP_INSERT = 0 将缓存的insert的记录插入的二级索引中
IBUF_OP_DELETE_MARK = 1 将缓存的标记delete标记的记录,标记到二级索引中。
IBUF_OP_DELETE = 2 purge线程操作,将真正的删除操作在二级索引也删除。
- ibuf的构建和其他
row_ins_sec_index_entry 3421
->row_ins_sec_index_entry_low 2999
->btr_cur_search_to_nth_level 1152
->ibuf_insert 3812
->use= ibuf_use
是否使用了ibuf
->no_counter = use <= IBUF_USE_INSERT
如果只是缓存INSERT ibuf,则no_counter为true
->switch (op)
对IBUF_OP_INSERT/IBUF_OP_DELETE_MARK/IBUF_OP_DELETE 分别处理
这里也就是判断操作和参数设置是否相符,如果不相符则不使用ibuf
->buf_pool_t* buf_pool = buf_pool_get(page_id)
->buf_page_t* bpage= buf_page_get_also_watch(buf_pool, page_id)
->if (bpage != NULL){DBUG_RETURN(FALSE)}
这里如果找到了page,则说明插入的page在buffer中,直接返回,不需要插入到ibuf中
->entry_size = rec_get_converted_size(index, entry, 0)
->if (entry_size>= page_get_free_space_of_empty(dict_table_is_comp(index->table))/ 2); DBUG_RETURN(FALSE)
如果记录过大大于page的1/2不能插入到ibuf中
->ibuf_insert_low
先尝试乐观插入 使用BTR_MODIFY_PREV
->ibuf_insert_low(BTR_MODIFY_PREV, op, no_counter,entry, entry_size,index, page_id, page_size, thr)
op为IBUF_OP_INSERT/IBUF_OP_DELETE_MARK/IBUF_OP_DELETE种的一种
index为插入的索引,一定为二级索引
no_counter 为前面得到,如果只有BUF_USE_INSERT操作,则为false
entry 为插入的数据
->do_merge = FALSE
->if (ibuf->size >= ibuf->max_size + IBUF_CONTRACT_DO_NOT_INSERT)
大于了最大值 则不再缓存,这里实际上就是参数
innodb_change_buffer_max_size指定的大小,默认为25%
->ibuf_contract(true)
触发一次合并操作
->ibuf_merge_pages
->btr_pcur_open_at_rnd_pos_func
->btr_cur_open_at_rnd_pos_func
->page_cur_open_on_rnd_user_rec
随机获取记录
->ibuf_get_merge_page_nos
在随机位置上获取page
->ibuf_get_merge_page_nos_func
合并page数量为IBUF_MERGE_AREA 8个page
返回从ibuff索引中page记录的总大小(8)和page数量
->buf_read_ibuf_merge_pages
->buf_read_ibuf_merge_pages(sync, space_ids, page_nos, *n_pages)
->buf_read_page_low(&err, sync && (i + 1 == n_stored),IORequest::IGNORE_MISSING, BUF_READ_ANY_PAGE, page_id,page_size, true)
->return(DB_STRONG_FAIL)
返回不能ibuf缓存
->heap = mem_heap_create(1024)
分配内存
->ibuf_entry = ibuf_entry_build(op, index, entry, page_id.space(), page_id.page_no(),no_counter ? ULINT_UNDEFINED : 0xFFFF, heap)
构建insert buffer中的记录,记录就是space id page no counter 和 每个实际的字段
如果no_counter为true则为ULINT_UNDEFINED,否则为 0xFFFF
no_counter为true代表只缓存IBUF_USE_INSERT,当前参数一般为ALL,因此一般为0xFFFF
->n_fields = dtuple_get_n_fields(entry);tuple = dtuple_create(heap, n_fields + IBUF_REC_FIELD_USER)
根据字段数量建立元组,IBUF_REC_FIELD_USER为4是什么意
这里的4是4个字段包含,1、space id 2、marker 3、page no 4、metadata 5、user field
的前面4个
->field = dtuple_get_nth_field(tuple, IBUF_REC_FIELD_SPACE);mach_write_to_4(buf, space); dfield_set_data(field, buf, 4);
写入space_id 4字节
->field = dtuple_get_nth_field(tuple, IBUF_REC_FIELD_MARKER);mach_write_to_1(buf, 0);dfield_set_data(field, buf, 1);
写入默认值为 0
->field = dtuple_get_nth_field(tuple, IBUF_REC_FIELD_PAGE);mach_write_to_4(buf, page_no);dfield_set_data(field, buf, 4);
写入page_no
->ti = type_info = static_cast<byte*>(mem_heap_alloc(heap,i + n_fields * DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE))
分配内存 IBUF_REC_INFO_SIZE + n_fields * DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE
IBUF_REC_INFO_SIZE 用于代表 IBUF_REC_OFFSET_TYPE IBUF_REC_OFFSET_FLAGS IBUF_REC_OFFSET_COUNTER的长度4字节
->switch (i)
准备写入IBUF_REC_FIELD_METADATA
case IBUF_REC_INFO_SIZE:
只考虑参数一般为ALL的情况
->mach_write_to_2(ti + IBUF_REC_OFFSET_COUNTER, counter);
->ti[IBUF_REC_OFFSET_TYPE] = (byte) op
->ti[IBUF_REC_OFFSET_FLAGS] = dict_table_is_comp(index->table)? IBUF_REC_COMPACT : 0
->ti += IBUF_REC_INFO_SIZE
这里写入IBUF_REC_OFFSET_COUNTER/IBUF_REC_OFFSET_TYPE/IBUF_REC_OFFSET_TYPE/IBUF_REC_OFFSET_FLAGS
等信息也就是IBUF_REC_FIELD_METADATA的前3信息,总部IBUF_REC_INFO_SIZE4字节大小
->for (i = 0; i < n_fields; i++)
->field = dtuple_get_nth_field(tuple, i + IBUF_REC_FIELD_USER)
获取ibuf 记录的字段位置
->entry_field = dtuple_get_nth_field(entry, i)
获取用户集中的字段
->dfield_copy(field, entry_field);
将用户字段考呗到ibuf记录中
->dtype_new_store_for_order_and_null_size(ti, dfield_get_type(entry_field), fixed_len)
设置DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE信息
->ti += DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE
->field = dtuple_get_nth_field(tuple, IBUF_REC_FIELD_METADATA);
->dfield_set_data(field, type_info, ti - type_info)
到这里IBUF_REC_FIELD_METADATA的信息才完全确认,因为各个字段的
DATA_NEW_ORDER_NULL_TYPE_BUF_SIZE的信息是需要循环每个字段
才能完成
->dtuple_set_types_binary(tuple, n_fields + IBUF_REC_FIELD_USER)
设置所有字段类型,完成后的记录类型如后面(1)
->if (BTR_LATCH_MODE_WITHOUT_INTENTION(mode) == BTR_MODIFY_TREE)
for循环
->ibuf_data_enough_free_for_insert()
用于判断是否可以插入的信息
->return(ibuf->free_list_len >= (ibuf->size / 2) + 3 * ibuf->height)
保留的free length比较大,大约为50%的空闲空间
->if (!ibuf_add_free_page())
是否可以对ibuf增加新page,新增page 加锁放入到free list中
主要是写入到PAGE_BTR_IBUF_FREE_LIST,并且增加
FIL_PAGE_IBUF_FREE_LIST长度
并且增加ibuffer内存中的free_list_len
ibuf->seg_size++;
ibuf->free_list_len++;
->如果都不可以则返回不能缓存
return(DB_STRONG_FAIL)
->btr_pcur_open(ibuf->index, ibuf_entry, PAGE_CUR_LE, mode, &pcur, &mtr)
使用ibuf_entry打开游标,定位到ibuf index的位置
->buffered = ibuf_get_volume_buffered(&pcur,page_id.space(),page_id.page_no(),op == IBUF_OP_DELETE ? &min_n_recs: NULL, &mtr);
这个函数用于返回如果合并当前ibuf 页后,可能导致的二级索引空间的变化量,如果是本条记录是IBUF_OP_DELETE操作
需要记录变化的行数
->if (op == IBUF_OP_DELETE && (min_n_recs < 2 || buf_pool_watch_occurred(page_id)))
导致二级索引页合并:如果是delete操作,并且二级索引中只有1条数据了或者已经再读取中,则不能缓存了,
因为可能合并后可能导致二级索引页合并
->err = DB_STRONG_FAIL;
标记错误不能缓存
goto func_exit;
->bitmap_page = ibuf_bitmap_get_map_page(page_id, page_size,&bitmap_mtr);
获取二级索引上的 bitmap block,
->if (op == IBUF_OP_INSERT)
如果缓存的是insert操作,需要坚持导致二级索引页分裂的情况
->bits = ibuf_bitmap_page_get_bits(bitmap_page, page_id, page_size, IBUF_BITMAP_FREE,&bitmap_mtr);
->if (buffered + entry_size + page_dir_calc_reserved_space(1)> ibuf_index_page_calc_free_from_bits(page_size, bits))
导致二级索引页分裂:如果本page合并后空间大于了剩余空间,合并可能导致二级索引页分裂
如果大于则不适合进行缓存
->ibuf_get_merge_page_nos(FALSE,btr_pcur_get_rec(&pcur), &mtr,space_ids, page_nos, &n_stored);
扫描ibuf 游标 page中 ibuf rec的space 和 page no 集合
->goto fail_exit
不适合缓存,但是读取本page中ibuf rec的page 进行merge
->if (!no_counter) {
ALL 都有no_counter
->ibuf_bitmap_page_set_bits(bitmap_page, page_id, page_size,IBUF_BITMAP_BUFFERED, TRUE,&bitmap_mtr);
设置bit map 的IBUF_BITMAP_BUFFERED为true,表本page进行了缓存操作,需要合并
- IBUF_BITMAP_FREE 2 0(0 bytes)、1(512 bytes)、2(1024 bytes)、3(2048 bytes)
- IBUF_BITMAP_BUFFERED 1 是否是ibuf操作
- IBUF_BITMAP_IBUF
为每个page记录信息,占用4位
->cursor = btr_pcur_get_btr_cur(&pcur);btr_cur_optimistic_insert;btr_cur_pessimistic_insert;
获取游标乐观或者悲观插入插入 到 ibuf索引中,本逻辑为乐观插入(BTR_MODIFY_PREV)
而BTR_MODIFY_TREE 可能是需要悲观插入的
func_exit:
->buf_read_ibuf_merge_pages(false, space_ids,page_nos, n_stored)
->ibuf_insert_low(BTR_MODIFY_TREE | BTR_LATCH_FOR_INSERT
如果乐观插入失败,则使用悲观插入
#0 ibuf_insert (op=IBUF_OP_DELETE_MARK, entry=0x7fff7c0216c0, index=0x7fff7c01d0f0, page_id=..., page_size=..., thr=0x7fff7c0571a0)
at /opt/percona-server-locks-detail-5.7.22/storage/innobase/ibuf/ibuf0ibuf.cc:3706
#1 0x0000000001bedf0f in btr_cur_search_to_nth_level (index=0x7fff7c01d0f0, level=0, tuple=0x7fff7c0216c0, mode=PAGE_CUR_LE, latch_mode=2, cursor=0x7fffec28e020, has_search_latch=0,
file=0x22ee2e0 "/opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0row.cc", line=1073, mtr=0x7fffec28e120)
at /opt/percona-server-locks-detail-5.7.22/storage/innobase/btr/btr0cur.cc:1166
#2 0x0000000001b0a37c in btr_pcur_open_low (index=0x7fff7c01d0f0, level=0, tuple=0x7fff7c0216c0, mode=PAGE_CUR_LE, latch_mode=4098, cursor=0x7fffec28e020,
file=0x22ee2e0 "/opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0row.cc", line=1073, mtr=0x7fffec28e120)
at /opt/percona-server-locks-detail-5.7.22/storage/innobase/include/btr0pcur.ic:465
#0 ibuf_entry_build (op=IBUF_OP_INSERT, index=0x7fff95abe310, entry=0x7fff940257f0, space=29989, page_no=1310, counter=65535, heap=0x7fff94156148)
at /opt/percona-server-locks-detail-5.7.22/storage/innobase/ibuf/ibuf0ibuf.cc:1779
#1 0x00000000019f6d8b in ibuf_insert_low (mode=36, op=IBUF_OP_INSERT, no_counter=0, entry=0x7fff940257f0, entry_size=16, index=0x7fff95abe310, page_id=..., page_size=..., thr=0x7fff940f95a0)
at /opt/percona-server-locks-detail-5.7.22/storage/innobase/ibuf/ibuf0ibuf.cc:3425
#2 0x00000000019f7ac5 in ibuf_insert (op=IBUF_OP_INSERT, entry=0x7fff940257f0, index=0x7fff95abe310, page_id=..., page_size=..., thr=0x7fff940f95a0)
at /opt/percona-server-locks-detail-5.7.22/storage/innobase/ibuf/ibuf0ibuf.cc:3812
#3 0x0000000001bede51 in btr_cur_search_to_nth_level (index=0x7fff95abe310, level=0, tuple=0x7fff940257f0, mode=PAGE_CUR_LE, latch_mode=2, cursor=0x7fffec28cc40, has_search_latch=0,
file=0x22dc098 "/opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0ins.cc", line=2999, mtr=0x7fffec28d000)
at /opt/percona-server-locks-detail-5.7.22/storage/innobase/btr/btr0cur.cc:1152
#4 0x0000000001abd939 in row_ins_sec_index_entry_low (flags=0, mode=2, index=0x7fff95abe310, offsets_heap=0x7fff940fae38, heap=0x7fff940fb2e8, entry=0x7fff940257f0, trx_id=0, thr=0x7fff940f95a0,
dup_chk_only=false) at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0ins.cc:2999
#5 0x0000000001abec00 in row_ins_sec_index_entry (index=0x7fff95abe310, entry=0x7fff940257f0, thr=0x7fff940f95a0, dup_chk_only=false)
at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0ins.cc:3421
#6 0x0000000001abedb0 in row_ins_index_entry (index=0x7fff95abe310, entry=0x7fff940257f0, thr=0x7fff940f95a0) at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0ins.cc:3470
#7 0x0000000001abf2d5 in row_ins_index_entry_step (node=0x7fff940f92f0, thr=0x7fff940f95a0) at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0ins.cc:3618
#8 0x0000000001abf65f in row_ins (node=0x7fff940f92f0, thr=0x7fff940f95a0) at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0ins.cc:3760
#9 0x0000000001abfc83 in row_ins_step (thr=0x7fff940f95a0) at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0ins.cc:3945
#10 0x0000000001adfb9f in row_insert_for_mysql_using_ins_graph (mysql_rec=0x7fff9405c980 "\374\001", prebuilt=0x7fff940f8d50)
at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0mysql.cc:2283
#11 0x0000000001ae012b in row_insert_for_mysql (mysql_rec=0x7fff9405c980 "\374\001", prebuilt=0x7fff940f8d50) at /opt/percona-server-locks-detail-5.7.22/storage/innobase/row/row0mysql.cc:2406
#12 0x000000000197ab14 in ha_innobase::write_row (this=0x7fff94025180, record=0x7fff9405c980 "\374\001") at /opt/percona-server-locks-detail-5.7.22/storage/innobase/handler/ha_innodb.cc:8344
#13 0x0000000000f640b6 in handler::ha_write_row (this=0x7fff94025180, buf=0x7fff9405c980 "\374\001") at /opt/percona-server-locks-detail-5.7.22/sql/handler.cc:8466
#14 0x000000000178fb66 in write_record (thd=0x7fff94000b70, table=0x7fff94046b10, info=0x7fff940075e8, update=0x7fff94007660) at /opt/percona-server-locks-detail-5.7.22/sql/sql_insert.cc:1881
struct ibuf_t{
ulint size; /*!< current size of the ibuf index
tree, in pages */
ulint max_size; /*!< recommended maximum size of the
ibuf index tree, in pages */
ulint seg_size; /*!< allocated pages of the file
segment containing ibuf header and
tree */
bool empty; /*!< Protected by the page
latch of the root page of the
insert buffer tree
(FSP_IBUF_TREE_ROOT_PAGE_NO). true
if and only if the insert
buffer tree is empty. */
ulint free_list_len; /*!< length of the free list */
ulint height; /*!< tree height */
dict_index_t* index; /*!< insert buffer index */
ulint n_merges; /*!< number of pages merged */
ulint n_merged_ops[IBUF_OP_COUNT];
/*!< number of operations of each type
merged to index pages */
ulint n_discarded_ops[IBUF_OP_COUNT];
/*!< number of operations of each type
discarded without merging due to the
tablespace being deleted or the
index being dropped */
};
Ibuf: size 1, free list len 0, seg size 2, 0 merges
ibuf page数量 合并次数
merged operations: merge 操作次数
insert 0, delete mark 0, delete 0
discarded operations: 抛弃次数
insert 0, delete mark 0, delete 0