创建聚集索引

一、ibuf_init_at_db_start

Creates the insert buffer data structure at a database startup and initializes the data structures for the insert buffer.

[cpp]  view plain  copy
  1. void  
  2. ibuf_init_at_db_start(void)  
  3. /*=======================*/  
  4. {  
  5.     page_t*     root;  
  6.     mtr_t       mtr;  
  7.     dict_table_t*   table;  
  8.     mem_heap_t* heap;  
  9.     dict_index_t*   index;  
  10.     ulint       n_used;  
  11.     page_t*     header_page;  
  12.     ulint       error;  
  13.   
  14.     ibuf = mem_alloc(sizeof(ibuf_t)); //The insert buffer control structure  
  15.   
  16.     memset(ibuf, 0, sizeof(*ibuf));  
  17.   
  18.     /* Note that also a pessimistic delete can sometimes make a B-tree 
  19.     grow in size, as the references on the upper levels of the tree can 
  20.     change */  
  21.   
  22.     ibuf->max_size = buf_pool_get_curr_size() / UNIV_PAGE_SIZE  
  23.         / IBUF_POOL_SIZE_PER_MAX_SIZE;  //此处为2,即设置最大为buffer pool的一半。  
  24.   
  25.     mutex_create(ibuf_pessimistic_insert_mutex_key,  
  26.              &ibuf_pessimistic_insert_mutex,  
  27.              SYNC_IBUF_PESS_INSERT_MUTEX);  
  28.   
  29.     mutex_create(ibuf_mutex_key,  
  30.              &ibuf_mutex, SYNC_IBUF_MUTEX);  
  31.   
  32.     mutex_create(ibuf_bitmap_mutex_key,  
  33.              &ibuf_bitmap_mutex, SYNC_IBUF_BITMAP_MUTEX);  
  34.   
  35.     mtr_start(&mtr);  
  36.   
  37.     mutex_enter(&ibuf_mutex);  
  38.   
  39.     mtr_x_lock(fil_space_get_latch(IBUF_SPACE_ID, NULL), &mtr);  
  40.   
  41.     header_page = ibuf_header_page_get(&mtr); //使用buf_page_get_gen()从space0:page3读取。  
  42.   
  43.     fseg_n_reserved_pages(header_page + IBUF_HEADER + IBUF_TREE_SEG_HEADER, //此处是fseg_header(ibuf_header中只有这一项),即指向root page的inode  
  44.                   &n_used, &mtr);  //Calculates the number of pages reserved by a segment, and how many pages are currently used.  
  45.     ibuf_enter(&mtr);  
  46.   
  47.     ut_ad(n_used >= 2); //n_used表示正在使用使用的page,此例正好为2  
  48.   
  49.     ibuf->seg_size = n_used;  
  50.   
  51.     {  
  52.         buf_block_t*    block;  
  53.   
  54.         block = buf_page_get(  
  55.             IBUF_SPACE_ID, 0, FSP_IBUF_TREE_ROOT_PAGE_NO, //page 4是insert buffer的root page  
  56.             RW_X_LATCH, &mtr);  
  57.         buf_block_dbg_add_level(block, SYNC_IBUF_TREE_NODE);   
  58.   
  59.         root = buf_block_get_frame(block);  
  60.     }  
  61.   
  62.     ibuf_size_update(root, &mtr); //此例更新ibuf->free_list_len=0,ibuf->height=1,ibuf->size=1(只有根页)  
  63.     mutex_exit(&ibuf_mutex);  
  64.   
  65.     ibuf->empty = (page_get_n_recs(root) == 0); //Gets the number of user records on page (the infimum and supremum records are not user records).  
  66.     ibuf_mtr_commit(&mtr);                      //此例ibuf->empty=1,确实为空。  
  67.   
  68.     heap = mem_heap_create(450);  
  69.   
  70.     /* Use old-style record format for the insert buffer. */  
  71.     table = dict_mem_table_create(IBUF_TABLE_NAME, IBUF_SPACE_ID, 1, 0); //IBUF_TABLE_NAME为"SYS_IBUF_TABLE",1表示表的列数  
  72.   
  73.     dict_mem_table_add_col(table, heap, "DUMMY_COLUMN", DATA_BINARY, 0, 0);  
  74.   
  75.     table->id = DICT_IBUF_ID_MIN + IBUF_SPACE_ID; //#define DICT_IBUF_ID_MIN 0xFFFFFFFF00000000ULL  
  76.   
  77.     dict_table_add_to_cache(table, heap);//加入数据字典,其中为table struct加了三个字段(见上一篇),并加入dict_sys的哈希表中。  
  78.     mem_heap_free(heap);  
  79.   
  80.     index = dict_mem_index_create(    //创建聚集索引结构体  
  81.         IBUF_TABLE_NAME, "CLUST_IND",  
  82.         IBUF_SPACE_ID, DICT_CLUSTERED | DICT_UNIVERSAL | DICT_IBUF, 1);  
  83.   
  84.     dict_mem_index_add_field(index, "DUMMY_COLUMN", 0);  
  85.   
  86.     index->id = DICT_IBUF_ID_MIN + IBUF_SPACE_ID;  
  87.   
  88.     error = dict_index_add_to_cache(table, index,  
  89.                     FSP_IBUF_TREE_ROOT_PAGE_NO, FALSE);//经过一番周折,将index链入到table->indexes  
  90.     ut_a(error == DB_SUCCESS);  
  91.   
  92.     ibuf->index = dict_table_get_first_index(table); 即table->indexes  
  93. }  

[cpp]  view plain  copy
  1. /** Insert buffer struct */  
  2. struct ibuf_struct{  
  3.     ulint       size;       /*!< current size of the ibuf index 
  4.                     tree, in pages */  
  5.     ulint       max_size;   /*!< recommended maximum size of the 
  6.                     ibuf index tree, in pages */  
  7.     ulint       seg_size;   /*!< allocated pages of the file 
  8.                     segment containing ibuf header and 
  9.                     tree */  
  10.     ibool       empty;      /*!< Protected by the page 
  11.                     latch of the root page of the 
  12.                     insert buffer tree 
  13.                     (FSP_IBUF_TREE_ROOT_PAGE_NO). TRUE 
  14.                     if and only if the insert 
  15.                     buffer tree is empty. */  
  16.     ulint       free_list_len;  /*!< length of the free list */  
  17.     ulint       height;     /*!< tree height */  
  18.     dict_index_t*   index;      /*!< insert buffer index */  
  19.   
  20.     ulint       n_merges;   /*!< number of pages merged */  
  21.     ulint       n_merged_ops[IBUF_OP_COUNT];  
  22.                     /*!< number of operations of each type 
  23.                     merged to index pages */  
  24.     ulint       n_discarded_ops[IBUF_OP_COUNT];  
  25.                     /*!< number of operations of each type 
  26.                     discarded without merging due to the 
  27.                     tablespace being deleted or the 
  28.                     index being dropped */  
  29. }  

这样,我们除了有一些SYS_*表(参考上一篇,它们也由dict_sys管理),还有一个dict_sys,它的哈希表中有很多dict_table_t结构体,这些结构体的index链接着表的索引。对应insert buffer,就是dict_sys---> dict_table_t ----->indexes.


二、流程

参考:《MySQL Innodb Insert Buffer/Checkpoint/Aio 实现分析》by 何登成;例子使用和该文中同样的例子。

row_insert_for_mysql--->row_ins_step--->row_ins--->row_ins_index_entry_step(ins_node_t* node, thr)---->row_ins_index_entry(node->index, ...)---->row_ins_index_entry_low(index, ...)---->btr_cur_search_to_nth_level(index, ...)

需要注意的是,row_ins_index_entry_step中对表的index作了遍历,对每个index,都调用了>row_ins_index_entry。所以,row_ins_index_entry_low(index, ...)的参数index可能是聚集索引,也可能是辅助索引,是辅助索引的时候才涉及到insert buffer(因为insert buffer在索引不唯一时才可以使用。)

在row_ins_index_entry_low中,先调用btr_cur_search_to_nth_level,之后对于聚集索引,调用btr_cur_optimistic_insert()真正的在页中插入数据;而对于非唯一辅助索引,则直接退出,插入过程交给merge线程。

1、准备工作

首先创建表:

[cpp]  view plain  copy
  1. CREATE TABLE `nkeys` ( `c1` int(11) NOT NULL, `c2` int(11) DEFAULT NULL, `c3` int(11) DEFAULT NULL, `c4` int(11) DEFAULT NULL, `c5` int(11) DEFAULT NULL, PRIMARY KEY (`c1`), UNIQUE KEY `c4` (`c4`), KEY `nkey1` (`c3`,`c5`) ) ENGINE=InnoDB;  

先向表中插入50000条数据,以保证索引有两层(不然根本不会使用insert buffer),使用以下方法;之后重启再插入一条数据(保证页不在buffer pool中)。 这样btr_cur_search_to_nth_level将会调用以下函数:ibuf_should_try、buf_page_get_gen、ibuf_insert

[cpp]  view plain  copy
  1. delimiter $$  
  2. drop procedure  if exists `test`.`insert_value_n` $$  
  3. create procedure insert_value_n(in n int) begin set @i=1; while @i<n do insert into nkeys values (@i,10,@i,@i,@i); set @i=@i+1; end while; end $$     //可以使用start transaction将整个插入过程作为一个事务,可加快插入,具体参考《Mysql技术内幕InnoDB存储引擎》7.8节。  
  4. delimiter ;  
  5. call insert_value_n(50000);  


2、ibuf_should_try用于判断是否可以使用insert buffer。它由btr_cur_search_to_nth_level调用。如果可以使用insert buffer,则设置buf_mode = btr_op == BTR_DELETE_OP ? BUF_GET_IF_IN_POOL_OR_WATCH : BUF_GET_IF_IN_POOL,对于插入操作,即设置buf_mod为BUF_GET_IF_IN_POOL。


3、buf_page_get_gen

由于页不在buffer pool中,且buf_mod为BUF_GET_IF_IN_POOL,所以直接返回block = NULL。


4、ibuf_insert--->ibuf_insert_low

If a thread attempts to buffer an insert on a page while a purge is in progress on the same page, the purge must not be buffered, because it could remove a record that was re-inserted later.  For simplicity, we block the buffering of all operations on a page that has a purge pending.


三、ibuf_insert_low

1、ibuf_entry_build()创建dtupl_t,即entry to insert into an ibuf index tree. 实际就是在原有dtupl_t上加了四个字段:space_id、marker、page number、type info。


2、btr_pcur_open(ibuf->index, ibuf_entry, PAGE_CUR_LE, mode, &pcur, &mtr)

ibuf->index为insert buffer的聚集索引,最终找到当前记录应该操作的 insert buffer 页面,操作位置记录在btr_pcur_t* pcur中,pcur->pos = BTR_PCUR_IS_POSITIONED,表示The persistent cursor is positioned by index search。其内部会调用btr_cur_search_to_nth_level()。

本例中首次使用insert buffer时,pcur中会指向page 4(保留页)中的首个记录(infimum),因为此时insert buffer中还没有数据。


3、ibuf_get_volume_buffered

 Find out the volume of already buffered inserts for the same index page.


4、bitmap_page = ibuf_bitmap_get_map_page(space, page_no, zip_size, &bitmap_mtr);

返回bitmap page,此例page_no(注意page_no是数据插入页,而不是ibuf中的页)较小,所以为第1页(保留页)。(问题:一个bitmap page,能表示多少个页的状态?16384)每个辅助索引页在bitmap页中占有4位:IBUF_BITMAP_FREE(2):0表示为可用空间,1表示剩余空间大于1/32页,2表示大于1/16,3表示大于1/8;IBUF_BITPAM_BUFFERED:1表示该索引页有记录被缓存在ibuf中;IBUF_BITMAP_IBUF:1表示该页为insert buffer的索引页。


5、根据 bitmap,计算索引页面中的空余空间,是否足够存放当前记录,并且不引起页面分裂。之后更新bitmap。


6、 btr_cur_optimistic_insert

其中btr_cur_ins_lock_and_undo() Check locks and write to the undo log, if specified(不过对于insert buffer,不会写undo log,此处只有聚集索引才会写undo log);page_cur_tuple_insert() Inserts a record next to page cursor,  returns pointer to inserted record if succeed.


四、merge

概括地说,Merge Insert Buffer的操作可能发生在以下几种情况下: 辅助索引页被读取到缓冲池时; Insert Buffer Bitmap页追踪到该辅助索引页已无可用空间时; Master Thread。

第一种情况为当辅助索引页被读取到缓冲池中时,例如这在执行正常的SELECT查询操作,这时需要检查Insert Buffer Bitmap页,然后确认该辅助索引页是否有记录存放于Insert Buffer B+树中。若有,则将Insert Buffer B+树中该页的记录插入到该辅助索引页中。可以看到对该页多次的记录操作通过一次操作合并到了原有的辅助索引页中,因此性能会有大幅提高。 

Insert Buffer Bitmap页用来追踪每个辅助索引页的可用空间,并至少有1/32页的空间。若插入辅助索引记录时检测到插入记录后可用空间会小于1/32页,则会强制进行一个合并操作,即强制读取辅助索引页,将Insert Buffer B+树中该页的记录及待插入的记录插入到辅助索引页中。这就是上述所说的第二种情况。 

还有一种情况,在Master Thread线程中每秒或每10秒会进行一次Merge Insert Buffer的操作,不同之处在于每次进行merge操作的页的数量不同。


以下从另一个角度来看merge:

1、master thread merge——主动merge

(1)先进行异步IO:ibuf_contract_for_n_pages(10个索引页)->ibuf_contract_ext,随机定位一个insert buffer 的页面,使用fil_io将该页面涉及的索引页读出(异步IO);

(2)再进行merge:io_handler_thread-->fil_aio_wait->buf_page_io_complete--->ibuf_merge_or_delete_for_page,判断当前页面是否存在insert buffer项(应当存在),在insert buffer中查找与该页相关的第一条记录,将这些记录插入的该索引页中,并删除在ibuf中的记录,之后设置ibuf bitmap。


以下为被动merge:

2、insert操作导致页面空间不足,或update导致页面空间不足,或purge导致页面为空,都会导致被动merge,因为insert buffer只能针对单页面,不能进行page split。

3、进行insert buffer操作时,发现insert buffer已经太大,ibuf_insert_low-->ibuf_contract(sync=true)(操作同步IO,不允许insert操作进行)。同样是随机定位一个insert buffer页面,将该页面中的所有更新合并到索引页中。
4、其他。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值