[MySQL 学习] Innodb Optimistic Insert流程

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介:

通常情况下,插入一条数据的接口函数为btr_cur_optimistic_insert,这时候不需要进行索引树分裂,先来看看这里怎么处理压缩表数据吧


btr_cur_optimistic_insert
a. 计算该Page上还能写入的最大空闲空间大小
max_size = page_get_max_insert_size_after_reorganize(page, 1);
以及这条逻辑记录(dtuple_struct)转换成物理记录的大小
rec_size = rec_get_converted_size(index, entry, n_ext)
b.判断该记录是否需要外部存储
if (page_zip_rec_needs_ext(rec_size, page_is_comp(page),
1320                    dtuple_get_n_fields(entry), zip_size))
如果需要外部存储的话,返回DB_TOO_BIG_RECORD,由上层继续处理
当为压缩表时,在每个压缩Page上的dense page directory中为每条记录记录预留两个byte。但没有record header。在一个空的leaf page上,至少要有一条记录。另外还要减去一个Byte来存储heap number. 同样的该记录也不能大于非压缩page最大可用空间的一半
所以判断条件为:
        (rec_size – (REC_N_NEW_EXTRA_BYTES – 2)
               >= (page_zip_empty_size(n_fields, zip_size) – 1)
               || rec_size >= page_get_free_space_of_empty(TRUE) / 2);
c.当该表为压缩表时,即zip_size>0,需要检查记录的大小,确保非叶子节点上能存储两条记录。否则返回DB_TOO_BIG_RECORD
一个压缩page的最大可用空闲空间,通过函数page_zip_empty_size计算

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()留的空间,用于存储索引信息

d.检查是否需要分裂叶子page以为未来的记录更新预留足够的空闲空间(预留空间为1k,也就是UNIV_PAGE_SIZE / 16)。当满足如下条件时:
1.聚集索引
2.该page上的记录数大于2个
3.叶子节点
4.UNIV_PAGE_SIZE/16+rec_size > max_size
5.是否分裂并将记录迁移到左节点或右节点(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

e.满足如下条件,返回DB_FAIL
1.max_size < BTR_CUR_PAGE_REORGANIZE_LIMIT || max_size < rec_size
BTR_CUR_PAGE_REORGANIZE_LIMIT为(UNIV_PAGE_SIZE / 32),表示如果一条插入记录空间不够,但重新组织page后可以获得这么多的空闲空间,就去重组织page。
2.page上的记录数大于1
3.插入记录的空间不够,即page_get_max_insert_size(page, 1) < rec_size
f. 调用函数btr_cur_ins_lock_and_undo检查锁并做undo
1.检查是否需要等待锁,如果被阻塞的话则加入一个显式锁
    err = lock_rec_insert_check_and_lock(flags, rec, 
                         btr_cur_get_block(cursor),
                         index, thr, mtr, inherit);
2.如果是聚集索引,且索引不是insert buffer tree,则记录undo,二级索引不记undo
        err = 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内偏移量决定。

      3.更新记录的回滚指针
            row_upd_index_entry_sys_field(entry, index,
                              DATA_ROLL_PTR, roll_ptr);
g.调用page_cur_tuple_insert尝试插入记录,
对于压缩表,调用函数page_cur_insert_rec_zip,会向解压page插入记录,向压缩page写入mlog,流程如下:
1.获取在该物理记录的大小。
rec_size = rec_offs_size(offsets);
2.检查压缩Page的modification log中是否有足够的空间,调用函数page_zip_available
先来看看一个压缩page包含哪些内容吧,在include/page0zip.ic文件的头部注释有详细的描述
(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重新插入一次记录。对于非压缩表,第二次插入不应该失败。

注:对于压缩表,在某些情况下似乎进行了两次重组织page,page_zip_reorganize的流程和btr_page_reorganize_low基本差不多,后面要证实这里是否有额外的开销
i.更新adaptive hash index (btr_search_update_hash_on_insert/btr_search_update_hash_node_on_insert)


j. if (!(flags & BTR_NO_LOCKING_FLAG) && inherit)   ==>lock_update_insert(block, *rec)
不太清楚锁这部分机制,inherit在函数lock_rec_insert_check_and_lock中被设置,如果插入记录的next_rec上在lock table中没有记录,则设为false,否则设为true.
k.对于非聚集索引,调用ibuf_update_free_bits_zip更新压缩page在insert buffer中的free bits

 


相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
打赏
0
0
0
0
10011
分享
相关文章
MySQL底层概述—10.InnoDB锁机制
本文介绍了:锁概述、锁分类、全局锁实战、表级锁(偏读)实战、行级锁升级表级锁实战、间隙锁实战、临键锁实战、幻读演示和解决、行级锁(偏写)优化建议、乐观锁实战、行锁原理分析、死锁与解决方案
104 24
MySQL底层概述—10.InnoDB锁机制
MySQL底层概述—5.InnoDB参数优化
本文介绍了MySQL数据库中与内存、日志和IO线程相关的参数优化,旨在提升数据库性能。主要内容包括: 1. 内存相关参数优化:缓冲池内存大小配置、配置多个Buffer Pool实例、Chunk大小配置、InnoDB缓存性能评估、Page管理相关参数、Change Buffer相关参数优化。 2. 日志相关参数优化:日志缓冲区配置、日志文件参数优化。 3. IO线程相关参数优化: 查询缓存参数、脏页刷盘参数、LRU链表参数、脏页刷盘相关参数。
MySQL底层概述—5.InnoDB参数优化
MySQL底层概述—4.InnoDB数据文件
本文介绍了InnoDB表空间文件结构及其组成部分,包括表空间、段、区、页和行。表空间是最高逻辑层,包含多个段;段由若干个区组成,每个区包含64个连续的页,页用于存储多条行记录。文章还详细解析了Page结构,分为通用部分(文件头与文件尾)、数据记录部分和页目录部分。此外,文中探讨了行记录格式,包括四种行格式(Redundant、Compact、Dynamic和Compressed),重点介绍了Compact行记录格式及其溢出机制。最后,文章解释了不同行格式的特点及应用场景,帮助理解InnoDB存储引擎的工作原理。
MySQL底层概述—4.InnoDB数据文件
MySQL底层概述—3.InnoDB线程模型
InnoDB存储引擎采用多线程模型,包含多个后台线程以处理不同任务。主要线程包括:IO Thread负责读写数据页和日志;Purge Thread回收已提交事务的undo日志;Page Cleaner Thread刷新脏页并清理redo日志;Master Thread调度其他线程,定时刷新脏页、回收undo日志、写入redo日志和合并写缓冲。各线程协同工作,确保数据一致性和高效性能。
MySQL底层概述—3.InnoDB线程模型
MySQL底层概述—2.InnoDB磁盘结构
InnoDB磁盘结构主要包括表空间(Tablespaces)、数据字典(Data Dictionary)、双写缓冲区(Double Write Buffer)、重做日志(redo log)和撤销日志(undo log)。其中,表空间分为系统、独立、通用、Undo及临时表空间,分别用于存储不同类型的数据。数据字典从MySQL 8.0起不再依赖.frm文件,转而使用InnoDB引擎存储,支持事务原子性DDL操作。
229 100
MySQL底层概述—2.InnoDB磁盘结构
MySQL底层概述—1.InnoDB内存结构
本文介绍了InnoDB引擎的关键组件和机制,包括引擎架构、Buffer Pool、Page管理机制、Change Buffer、Log Buffer及Adaptive Hash Index。
238 97
MySQL底层概述—1.InnoDB内存结构
MySQL原理简介—2.InnoDB架构原理和执行流程
本文介绍了MySQL中更新语句的执行流程及其背后的机制,主要包括: 1. **更新语句的执行流程**:从SQL解析到执行器调用InnoDB存储引擎接口。 2. **Buffer Pool缓冲池**:缓存磁盘数据,减少磁盘I/O。 3. **Undo日志**:记录更新前的数据,支持事务回滚。 4. **Redo日志**:确保事务持久性,防止宕机导致的数据丢失。 5. **Binlog日志**:记录逻辑操作,用于数据恢复和主从复制。 6. **事务提交机制**:包括redo日志和binlog日志的刷盘策略,确保数据一致性。 7. **后台IO线程**:将内存中的脏数据异步刷入磁盘。
【MySQL进阶篇】存储引擎(MySQL体系结构、InnoDB、MyISAM、Memory区别及特点、存储引擎的选择方案)
MySQL的存储引擎是其核心组件之一,负责数据的存储、索引和检索。不同的存储引擎具有不同的功能和特性,可以根据业务需求 选择合适的引擎。本文详细介绍了MySQL体系结构、InnoDB、MyISAM、Memory区别及特点、存储引擎的选择方案。
【MySQL进阶篇】存储引擎(MySQL体系结构、InnoDB、MyISAM、Memory区别及特点、存储引擎的选择方案)
MySQL存储引擎详述:InnoDB为何胜出?
MySQL 是最流行的开源关系型数据库之一,其存储引擎设计是其高效灵活的关键。InnoDB 作为默认存储引擎,支持事务、行级锁和外键约束,适用于高并发读写和数据完整性要求高的场景;而 MyISAM 不支持事务,适合读密集且对事务要求不高的应用。根据不同需求选择合适的存储引擎至关重要,官方推荐大多数场景使用 InnoDB。
100 7
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件
本文介绍了MySQL InnoDB存储引擎中的数据文件和重做日志文件。数据文件包括`.ibd`和`ibdata`文件,用于存放InnoDB数据和索引。重做日志文件(redo log)确保数据的可靠性和事务的持久性,其大小和路径可由相关参数配置。文章还提供了视频讲解和示例代码。
234 11
【赵渝强老师】MySQL InnoDB的数据文件与重做日志文件