通常情况下,插入一条数据的接口函数为btr_cur_optimistic_insert,这时候不需要进行索引树分裂,先来看看这里怎么处理压缩表数据吧
zip_size-
(PAGE_DATA // (PAGE_HEADER + 36 + 2 * FSEG_HEADER_SIZE),表示一个page上数据开始的偏移量
+PAGE_ZIP_DIR_SLOT_SIZE //一个压缩页内directory entry的大小,2个字节
+DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN 一个记录的事务ID和回滚段指针长度,6+7个字节
+1 /* encoded heap_no==2 in page_zip_write_rec() */
+1 /* end of modification log */
)
-compressBound(2 * (n_fields + 1)) //减去为page_zip_fields_encode()留的空间,用于存储索引信息
1.聚集索引2.该page上的记录数大于2个3.叶子节点4.UNIV_PAGE_SIZE/16+rec_size > max_size5.是否分裂并将记录迁移到左节点或右节点(btr_page_get_split_rec_to_right || btr_page_get_split_rec_to_left)判断新插入记录是在上次插入的记录之后,还是之前,据此判断将新记录插入分裂的左节点还是右节点
btr_page_get_split_rec_to_right
当前插入记录的下一个记录指针等于上次插入的记录指针时,返回TRUE,否则返回false
btr_page_get_split_rec_to_right
当前插入的记录在该page上上次插入记录的右边,则认为这是一次序列插入,返回TRUE,否则返回False
若满足上述条件,返回 DB_FAIL
1.max_size < BTR_CUR_PAGE_REORGANIZE_LIMIT || max_size < rec_sizeBTR_CUR_PAGE_REORGANIZE_LIMIT为(UNIV_PAGE_SIZE / 32),表示如果一条插入记录空间不够,但重新组织page后可以获得这么多的空闲空间,就去重组织page。2.page上的记录数大于13.插入记录的空间不够,即page_get_max_insert_size(page, 1) < rec_size
1.检查是否需要等待锁,如果被阻塞的话则加入一个显式锁err = lock_rec_insert_check_and_lock(flags, rec,btr_cur_get_block(cursor),index, thr, mtr, inherit);2.如果是聚集索引,且索引不是insert buffer tree,则记录undo,二级索引不记undoerr = trx_undo_report_row_operation(flags, TRX_UNDO_INSERT_OP,thr, index, entry,NULL, 0, NULL,&roll_ptr);
(1)事务的rollback segment已经指定(trx_assign_rseg,循环从trx_sys->rseg_list上获取,回滚段的数目由参数innodb_rollback_segments来控制)
(2)如果没有分配trx_undo_t(即trx->insert_undo == NULL),则为当前事务分配一个trx_undo_assign_undo
<1>首先调用trx_undo_reuse_cached从rseg->insert_undo_cached上获取undo log对象,初始化undo和对应的undo page(trx_undo_insert_header_reuse)
<2>如果<1>没有cache的undo,则创建一个新的trx_undo_t对象(trx_undo_create)
–>调用函数trx_undo_seg_create,首先从回滚段头部读取slot信息,尝试找到空闲的undo log slot,如果没有的话,则报warning DB_TOO_MANY_CONCURRENT_TRXS;并为undo log分配回滚page,
–>为undo page 初始化undo page header(trx_undo_header_create),分配undo log的内存结构trx_undo_t(trx_undo_mem_create)
<3>加入到rseg->insert_undo_list链表头部,并设置trx->insert_undo = undo
(3). 将undo page读入内存,并向其中写入记录信息(insert操作为trx_undo_page_report_insert,update操作为trx_undo_page_report_modify),并创建回滚指针(trx_undo_build_roll_ptr),回滚指针由操作类型、回滚段id、undo page no以及page内偏移量决定。
1.获取在该物理记录的大小。rec_size = rec_offs_size(offsets);2.检查压缩Page的modification log中是否有足够的空间,调用函数page_zip_available
(1)uncompressed page header (大小为PAGE_DATA字节) (2)Compressed index information //未明(3)Compressed page data (4)Page modification log (page_zip->m_start..page_zip->m_end) //写入记录时包含记录数据,删除记录时,直接在mlog中标记删除 (5)Empty zero-filled space (6)BLOB pointers (on leaf pages)– BTR_EXTERN_FIELD_REF_SIZE for each externally stored column- in descending collation orde (7)Uncompressed columns of user records, n_dense * uncompressed_size bytes, indexed by heap_no //包括node_ptr(non-leaf B-tree nodes; level>0),trx_id和roll_ptr(leaf B-tree nodes; level=0) ps:facebook正在对这部分优化,进行压缩– DATA_TRX_ID_LEN + DATA_ROLL_PTR_LEN for leaf pages of clustered indexes- REC_NODE_PTR_SIZE for non-leaf pages – 0 otherwise
(8)dense page directory, stored backwards //dense page directory指向每一个page上的用户记录,包括标记删除的记录,不包括infimum/supremum记录,每个entry的两个最高有效位为delete-mark和n_owned标记预留,其中n_onwned标记表示非压缩page上记录的spare index一个slot对应记录链表的最后一个记录。– n_dense = n_heap – 2- existing records in ascending collation order – deleted records (free list) in link order
3.如果压缩page中不够容纳新的mlog记录,则
(1)调用page_cur_insert_rec_low向非压缩页中插入数据,包括实际插入数据,以及page内记录的spare index更新
<1>计算物理记录大小及获得page内的空闲空间,先从free list上找,如果没有或空间太小,再从堆上分配
<2>将记录插入到page中的记录链上,设置n_owned和heap_no
<3>更新Page头部PAGE_LAST_INSERT/PAGE_DIRECTION/信息
<4>更新spare index的slot记录信息,n_owned+1,如果n_owned超过PAGE_DIR_SLOT_MAX_N_OWNED,还需要对slot进行分裂(page_dir_split_slot)
<5>写入日志信息(page_cur_insert_rec_write_log)
(2)如果(1)插入成功,则对压缩页进行重新压缩(page_cur_insert_rec_zip_reorg)
<1>调用page_zip_compress对page进行压缩,如果压缩成功,则返回
<2>如果压缩失败,则调用函数page_zip_reorganize对page进行重新组织压缩page。
<<1>>将page内容拷贝到一个临时block中,重建page(page_create),再从临时空间拷贝记录(page_copy_rec_list_end_no_locks)
<<2>>最后再做一次压缩page_zip_compress,如果失败则返回FALSE。如果成功,则调用lock_move_reorganize_page更新lock table,然后返回TRUE
<3>如果重组织page后压缩依然失败,则page_zip_decompress,并返回NULL
最后返回插入的记录或NULL
4.如果压缩page能容纳新的mlog记录,则继续往下走
(1)从非压缩page中找到空闲空间,以用于放置记录,跟函数 page_cur_insert_rec_low 的流程差不多
(2)向压缩页的dense page directory中增加一个slot,调用函数page_zip_dir_add_slot
(3)设置插入记录的前后记录指针,设置插入记录的n_owned为0,并设置heap_no
(4)向dense page directory中写入记录信息(page_zip_dir_insert)
(5)同时更新压缩和非压缩Page头部信息
(6)更新压缩页和非压页该记录的owner记录的n_owned字段加1,如果n_owned超过PAGE_DIR_SLOT_MAX_N_OWNED,还需要分裂slot.
(7)向压缩page中插入记录page_zip_write_rec(page_zip, insert_rec, index, offsets, 1);
<1>找到记录在dense page directory中的slot = page_zip_dir_find(page_zip, page_offset(rec));
<2>设置slot上的删除标记
<3>获取记录heap no heap_no = rec_get_heap_no_new(rec);
<4>从page_zip->data + page_zip->m_end开始写入mlog,包括heap_no-1,从(rec – REC_N_NEW_EXTRA_BYTES)~rec – rec_offs_extra_size(offsets)间的字节(物理记录之外额外信息)。
<5>
对于leaf page
–>如果是聚集索引,需要存储trx_id及roll_ptr以及blob列的BTR_EXTERN_FIELD_REF信息,如果存在外部存储的列,则调用函数page_zip_write_rec_ext来向压缩page中写入记录。trx_id和roll_ptr属于非压缩的部分,也会被单独写(存储位置为page_zip_dir_start(page_zip)),但都会写入到mlog中,包括记录本身
–>如果是非聚集索引,只需要在mlog中记录完整的记录
对于non-leaf page
–>拷贝除node_ptr之外的数据到mlog中
–>拷贝node_ptr到非压缩区域
<6>最后更新page_zip->m_end
5.最后记录日志信息page_cur_insert_rec_write_log
h.如果插入记录失败,则调用btr_page_reorganize->btr_page_reorganize_low重组织page,并再次调用page_cur_tuple_insert重新插入一次记录。对于非压缩表,第二次插入不应该失败。