mysql innodb insert_[MySQL 源码] Innodb Pessimistic Insert流程-阿里云开发者社区

1)选择作为分裂中间点的记录;

先介绍涉及到的几个宏

#define FSP_UP      ((byte)111) /*!< alphabetically upwards */

#define FSP_DOWN    ((byte)112) /*!< alphabetically downwards */

#define FSP_NO_DIR  ((byte)113) /*!< no order */

这几个宏决定记录插入新分裂页的顺序,是按照字母上升顺序,还是下降顺序。

分三种情况来决定分裂记录和方向

<1>.已经做过一次split,但记录依然无法插入成功,则继续进行分裂

direction = FSP_UP;

hint_page_no = page_no + 1;

split_rec = btr_page_get_split_rec(cursor, tuple, n_ext); //查找一个分裂记录

if (UNIV_UNLIKELY(split_rec == NULL)) {

insert_left = btr_page_tuple_smaller(

cursor, tuple, offsets, n_uniq, &heap);

<2>.如果函数btr_page_get_split_rec_to_right返回TRUE,则:

direction = FSP_UP;

int_page_no = page_no + 1;

函数btr_page_get_split_rec_to_righ流程如下:

首先判断本次插入记录是否在上次插入记录的右边,如果是的话,则认为这是顺序的插入,然后执行如下:

–>获取当前插入记录的下一个记录,如果是supremum record,则split_rec=NULL,

–>再次获得下下条记录,也就是说,会向后看两条记录,这两条记录有一条为supremum record,split_rec都会被设置为NULL,

否则设置split_rec为第二个记录。

这样做的目的是,如果从插入点开始有超过两个用户记录,我们在该page上保留一个记录,这样随后的序列插入可以利用自适应哈希,因为他们可以简单的通过查看这个page上的记录,来检查正确的查找位置(翻译自注释,待证实),split_rec为NULL表示从新插入的记录开始分裂.

–>返回TRUE

如果是随机插入的话,返回FALSE

<3>如果函数btr_page_get_split_rec_to_left返回TRUE,则

direction = FSP_DOWN;

hint_page_no = page_no – 1;

ut_ad(split_rec);

这种情况下split_rec不可为空

函数btr_page_get_split_rec_to_left流程如下

首先判断,如果上次插入的记录在当前插入记录的右边,则继续下面的流程,否则返回FALSE

–>如果插入的记录不是当前page上的第一个记录,也就是不在infimum记录的下一个,设置split_rec为当前插入点

–>否则,设置split_rec为当前记录插入点的下一个

–>返回TRUE

<4>如果上述都不满足

direction = FSP_UP;

hint_page_no = page_no + 1;

如果page上记录不止1个,则从中间开始分裂

split_rec = page_get_middle_rec(page);

如果当前插入记录比该page上记录要小(btr_page_tuple_smaller),则

split_rec = page_rec_get_next(

page_get_infimum_rec(page));

否则,split_rec为NULL

也就是说,当这个page上只有一个记录时,我们不能从中间做分裂,而是需要决定新节点是插入到左边还是右边节点。

综上,只有当上次插入的记录在当前插入点的右边时,才会设置direction = FSP_UP,其他情况都是FSP_DOWN

2)为索引分配一个新Page

new_block = btr_page_alloc(cursor->index, hint_page_no, direction,

btr_page_get_level(page, mtr), mtr);

new_page = buf_block_get_frame(new_block);

new_page_zip = buf_block_get_page_zip(new_block);

btr_page_create(new_block, new_page_zip, cursor->index,

btr_page_get_level(page, mtr), mtr);

3)计算上半部分的page的第一个记录,以及在原始page上的第一个记录

<1> split_rec 不为空

first_rec = move_limit = split_rec;

offsets = rec_get_offsets(split_rec, cursor->index, offsets,

n_uniq, &heap);

insert_left = cmp_dtuple_rec(tuple, split_rec, offsets) < 0;

insert_left表示当前记录是插入到split_rec的左边还是右边。

<2>insert_left为TRUE //只有在n_iterations>0时才会发生

first_rec = page_rec_get_next(page_get_infimum_rec(page));

move_limit = page_rec_get_next(btr_cur_get_rec(cursor));

<3>其他(split_rec为空)

buf = mem_alloc(rec_get_converted_size(cursor->index,

tuple, n_ext));

first_rec = rec_convert_dtuple_to_rec(buf, cursor->index,

tuple, n_ext);

move_limit = page_rec_get_next(btr_cur_get_rec(cursor));

first_rec为插入的记录

move_limit为当前插入记录的下一条

4)修改B树结构

<1>将新page attach到btree上对应的层次上,并向上层节点插入node指针,更新当前层次的节点链表指针

btr_attach_half_pages(cursor->index, block,

first_rec, new_block, direction, mtr);

<2>判断新记录是否能够满足插入。

–>split_rec不为空:

insert_will_fit = !new_page_zip

&& btr_page_insert_fits(cursor, split_rec,

offsets, tuple, n_ext, heap);

对于压缩表,new_page_zip不为空,也就是说insert_wil_fit总是为FALSE。

–>split_rec为空,表示分裂记录就是当前插入记录

insert_will_fit = !new_page_zip

&& btr_page_insert_fits(cursor, NULL,

NULL, tuple, n_ext, heap);

同样的insert_will_fit对于压缩表而言,总是为FALSE。这意味着在索引分裂的过程中,会一直持有索引上的排他锁

这也会导致压缩表的分裂开销非常大。

<3>判断是否释放索引上的排他锁

if (insert_will_fit && page_is_leaf(page)) {

mtr_memo_release(mtr, dict_index_get_lock(cursor->index),

MTR_MEMO_X_LOCK);

}

5)开始做记录迁移,根据direction的不同,走不同的分支,流程大同小异,这里我们主要看一下FSP_UP

<1>看该函数的返回值:

page_move_rec_list_end(new_block, block, move_limit,

cursor->index, mtr)

将block上从move_limit(包含该记录)开始记录拷贝到new_block中。

–>page_copy_rec_list_end(new_block, block,

split_rec, index, mtr)

实际拷贝动作,在拷贝完成后,还会对新page做compress

如果返回false,表示压缩失败,直接返回FALSE。不继续下面的流程

–>从block上删除转移的记录。

page_delete_rec_list_end(split_rec, block, index,

new_n_recs – old_n_recs,

new_data_size – old_data_size, mtr);

–>返回TRUE

如果转移记录到新block,但新block压缩失败的话,还需要继续做处理:

–>将原page中的记录拷贝到新page中。

page_zip_copy_recs(new_page_zip, new_page,

page_zip, page, cursor->index, mtr);

–>从新page上删除从move_limit开始的记录,不包括move_limit记录以及系统记录

page_delete_rec_list_start(move_limit – page

+ new_page, new_block,

cursor->index, mtr);

–>更新锁表和AHI

lock_move_rec_list_end(new_block, block, move_limit);

btr_search_move_or_delete_hash_entries(

new_block, block, cursor->index);

–>从源表上删除从move_limit(包括move_limit)开始的记录

page_delete_rec_list_end(move_limit, block,

cursor->index,

ULINT_UNDEFINED,

ULINT_UNDEFINED, mtr);

最后更新记录锁

left_block = block;

right_block = new_block;

lock_update_split_right(right_block, left_block);

6)到了这一步,索引树的分裂和修改已经结束了,这时候可以把记录插入到其中,根据insert_left来选择插入到哪个block中

7)如果插入失败,则重新reorgnize page,n_iterations++;然后重头开始继续分裂。

8)对于二级索引,更新insert buffer.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值