MySQL InnoDB 事务commit逻辑分析

news2024/11/15 5:51:58

一、前言

事务提交是事务即将落盘的一系列操作,涉及redo\undo log的写盘、bin log的写盘、事务状态的重置、各种参数的改变、无用undo log的清理等方方面面。

在commit过程会有两个阶段:一个是prepare阶段,入口函数为trx_prepare_low;另一个就是commit阶段。

prepare阶段和BINLOG做XA,分别设置insert undo 和 update undo的状态为prepare,调用函数trx_undo_set_state_at_prepare,过程也比较简单,找到undo log slot对应的头页面(trx_undo_t::hdr_page_no),将页面段头的TRX_UNDO_STATE设置为TRX_UNDO_PREPARED,同时修改其他对应字段。

commit阶段较为复杂,也是接下来要详细讲述的。

在InnoDB存储引擎层的commit阶段的入口函数为innobase_commit,它的总的结构大体如下:

二、基本概念

1、事务的状态

事务状态的宏定义在文件trx0types.h中,事务有五种状态,在源代码中的定义为:

其中每个参数的含义为:

参数名称

含义

TRX_STATE_NOT_STARTED

事务尚未开始

TRX_STATE_FORCED_ROLLBACK

事务强制回滚

TRX_STATE_ACTIVE

事务处于活跃状态

TRX_STATE_PREPARED

事务处于准备状态

TRX_STATE_COMMITTED_IN_MEMORY

事务在内存中已提交

2、回滚日志的状态

回滚日志的状态的改变主要发生在trx_undo_set_state_at_finish函数,针对inisert和update两种类型的undo log做出不同的状态赋值。

三、逻辑分析

innobase_commit

在InnoDB存储引擎中进行事务的提交主要的调用的函数是innobase_commit,文件位于ha_innodb.cc。它的函数调用流程图为:

主要逻辑为:首先会判断是否将事务标记为异步回滚,如果是,则将事务回滚,调用的函数为innobase_rollback,如果不是,则会判断是否向MySQL 2PC协调器注册了事务。

接着提取出事务中read_only参数的值,判断是否标记为只读事务。调用thd_binlog_pos读取正在提交的事务的二进制日志位置。

接下来就是调用innobase_commit_low这个重要的函数,这个函数主要是在InnoDB数据库中提交一个事务。下一小节我们重点分析这个函数。

innobase commit完后,判断read_only参数,如果不是只读,则会将commit_threads数减1。检测的cond_signal,调用的函数为mysql_cond_signal。

接着就是执行写和刷新日志的操作。调用的函数为trx_copmmit_complete_for_mysql,将日志刷新到磁盘。

如果只是将sql语句标记位结束,并且不执行事务的提交。如果在此sql语句中为某些表保留了自动增量锁,则立即释放它,调用的函数为lock_unlock_table_autoinc。

并且将事务的undo_no保存,以便在必须回滚下一条sql语句时知道要回滚的位置。

最后重置所需的AUTO-INC行数和fts_next_doc_id,强制线程离开InnoDB,调用的函数为innobase_srv_conc_force_exit_innodb。

trx_commit_for_mysql

接着调用innobase_commit_low,然后调用trx_commit_for_mysql,它位于trx0trx.cc文件中。它的逻辑流程图为:

因为不通过向事务发送Innobase信号来进行提交,所以在这里必须确保trx已经启动。然后判断trx->state,如果是TRX_STATE_NOT_STARTED或TRX_STATE_FORCED_ROLLBACK状态,则进入到trx_start_low函数,如果是TRX_STATE_ACTIVE或TRX_STATE_PREPARED状态,则进入到trx_commit函数。

trx_commit

trx_commit函数,是用来提交一个事务的。它同样位于trx0trx.cc文件中,这个函数中需要用到mini事务,用一个mini事务来完成对于接下来的commit操作。它的流程图如下:

首先会检查一下redo/noredo 的回滚段是否有insert\update的修改操作,调用的函数是trx_is_rseg_updated,直接判断它的返回值即可,如果有,则会同步一个mini事务。接着进入到trx_commit_low函数。

trx_commit_low

trx_commit_low不光提交一个事务,而且还提交一个mini事务。传进来的mtr参数就是用于提交一个mini事务。

在trx_commit_low函数中,声明自动提交的非锁定选项不能在rw_trx_list中,并且它是只读事务。并且事务必须位于mysql_trx_list中。trx_commit_low的流程图如下:

如果我们正在执行最后的提交,则undo_no的值为非零,所以首先判断一下trx中的fts_trx和undo_no参数是否为零,不为零则从FTS系统的POV进行一些必要的操作。接着判断传进来的参数mtr是否为空,mini事务不为空,mtr请求将来提交以使其同步,接着才能进行将序列号写入到history list中,并且通过trx_write_serialisation_history将serialised参数的值返回,接下来调用mtr_commit进行mini事务的提交,使整个事务在基于文件中提交;mtr为空则将serialised参数的值置为false。

最后调用trx_commit_in_memory函数在内存中提交一个事务。

先看trx_write_serialisation_history函数的具体逻辑,然后在看trx_commit_in_memory函数。

trx_write_serialisation_history

history list需要持久化在磁盘上,在undo log header的最后有对它的定义,在源代码trx0undo.h文件中可以看到:

函数trx_write_serialisation_history的作用是为事务分配其历史序列号,并且将update undo log记录写入到分配的回滚段。如果写入了序列化日志,则函数的返回值为true,否则为false,它的流程图如下:

1、trx_undo_set_state_at_finish

首先,获取到回滚段的mutex,对回滚段中两个slot(m_redo、m_noredo)都进行mutex_enter。然后将insert undo设置成完成状态,调用的函数为trx_undo_set_state_at_finish。函数trx_undo_set_state_at_finish首先会获取到一个undo log页(trx_undo_page_get)的地址,然后判断并设置事务的完成状态。完成状态一共有三种:

a、如果undo log页的大小为1,并且占用的header page使用大小小于TRX_UNDO_PAGE_REUSE_LIMIT(3 * UNIV_PAGE_SIZE / 4)时,则将状态设置为TRX_UNDO_CACHED,该undo对象会随后加入到undo cached list上,以备重用。trx0undo.h中的定义:

b、如果是insert undo(type为TRX_UNDO_INSERT),则状态设置为TRX_UNDO_TO_FREE,这也说明insert类型的undo log在commit完后,会直接释放,不会再保留。

c、如果不是上两种情况,也就是delete和update操作产生的update undo log类型的日志,则需要purge线程适时进行清理操作,状态设置为TRX_UNDO_TO_PURGE。三种状态的设置:

在undo的状态信息state设置好后,将state写入到undo header页的TRX_UNDO_STATE位置中,调用的函数为mlog_write_ulint。

insert undo设置完完成状态后,接下来对update undo进行设置完成状态。首先获取到回滚段的两个slot(m_redo、m_noredo)中的update_undo链表地址,然后将回滚段添加到purge queue,调用的函数为trx_serialisation_number_get,它在函数中的比较重要的实现语句为purge_sys->purge_queue->push(elem)。然后进行完成状态的设置,调用的函数与insert设置完成状态的函数一样,都是trx_undo_set_state_at_finish,这里就不在重复介绍。

然后就是对update undo进行清理,调用的函数为trx_undo_update_cleanup

2、trx_undo_update_cleanup

它将update undo log header添加为历史记录列表中的第一个,并释放内存对象,或将其放入缓存的update undo log 段列表中。它的具体实现为:

首先会获取到update_undo以及rseg,然后调用trx_purge_add_update_undo_to_history函数,将update_log添加到history链表中,purge线程在合适的时间进行清理。

添加完后,将undo log从update_undo_list中移除,重新赋值为NULL,然后将undo log添加到update_undo_cached链表中,最后在内存中释放。这样就可以重用这些已经生成的undo log,不用重新创建生成,大大节省了效率。

其中比较重要的函数是trx_purge_add_update_undo_to_history函数,接下来重点分析这个这个函数。

(1)、trx_purge_add_update_undo_to_history

它的流程步骤如下:

a、首先,获取undo回滚段头,调用的函数为trx_rsegf_get,通过upda_page和undo->hdr_offset确定undo_header的值。

b、判断undo状态是否为cached(TRX_UNDO_CACHED)。如果不是cached状态,则设置第n个回滚日志插槽的文件页号为FIL_NULL,意为slot空闲,调用的函数为trx_rsegf_set_nth_undo。同时更新TRX_RSEG_HISTORY_SIZE的大小(大小为原来TRX_RSEG_HISTORY_SIZE的大小+undo->size),读取调用的函数为mtr_read_ulint,写入调用的函数为mlog_write_ulint,写入的大小为4byte(MLOG_4BYTES)。

c、将undo日志添加为历史记录列表(TRX_RSEG_HISTORY)中的第一条,节点指针为undo header的TRX_UNDO_HISTORY_NODE,调用的函数为flst_add_first。

d、根据update_rseg_history_len的值对trx_sys->rseg_history_len进行更新(也就是show engine innodb status看到的history list),如果只有普通的update_undo,则加1,如果还有临时表的update_undo,则加2,调用的函数为os_atomic_increment_ulint;然后唤醒purge线程,调用的函数为srv_wake_purge_thread_if_not_active。

e、将事务编号写入回滚日志头。将trx->no写到undo头的TRX_UNDO_TRX_NO段,调用的函数为mlog_write_ull。

f、判断undo log的已删除标记del_marks的值,如果不为空则将del_marks跟新为false,写入到TRX_UNDO_DEL_MARKS段,大小为2byte(MLOG_2BYTES)。

g、如果undo所在回滚段中的rseg->last_page_no为FIL_NULL,表示该回滚段的旧的清理已经完成,进行如下赋值,记录这个回滚段上第一个需要purge的undo记录信息:

rseg->last_page_no = undo->hdr_page_no;

rseg->last_offset = undo->hdr_offset;

rseg->last_trx_no = trx->no;

rseg->last_del_marks = undo->del_marks;

(2)、trx_undo_mem_free

将undo log放到history list后,就可以将update undo置为空(undo_ptr->update_undo = NULL)。如果undo需要cache,将undo对象放到回滚段的update_undo_cached链表上;否则释放undo对象,释放调用函数为trx_undo_mem_free。

3、trx_sys_update_mysql_binlog_offset

最后,如果启用了MySQL Binlogging或数据库服务器是MySQL复制从服务器,则更新trx sys header中的最新MySQL Binlog名称和偏移信息,在trx0sys.h文件中有对trx系统header中关于MySQL binlog偏移量信息的偏移量的定义:

写binlog调用的函数为trx_sys_update_mysql_binlog_offset,它的流程大体如下:

a、调用trx_sysf_get获取事务系统头sys_header。

b、调用mlog_write_ulint函数更新TRX_SYS_MYSQL_LOG_MAGIC_N_FLD段。

c、调用mlog_write_string函数更新TRX_SYS_MYSQL_LOG_NAME段。

d、调用mlog_write_ulint函数更新TRX_SYS_MYSQL_LOG_OFFSET_HIGH段。

e、调用mlog_write_ulint函数更新TRX_SYS_MYSQL_LOG_OFFSET_LOW段。

trx_commit_in_memory

函数trx_commit_in_memory用于在内存中提交事务。它的流程图如下:

首先,确定事务是否为非锁定自动提交选择(表示为只读),调用的函数为trx_is_autocommit_non_locking,如果是非锁定自动提交选择,则判断read_view是否存在,若存在,则关闭read_view,并且将事务的状态设置为TRX_STATE_NOT_STARTED。如果不是非锁定自动提交选择,为了获得一致的快照,需要在执行提交和释放锁之前从正在运行的mvcc的事务ID列表中删除当前事务,调用的函数为trx_erase_lists;这样,该事务不再拥有任何用户锁,然后从活跃事务列表中删除该事务,当从rw_trx_list中删除trx时,此调用还将释放所有隐式锁,调用的函数为lock_trx_release_locks。

事务释放完记录锁、从读写事务链表中清除、以及关闭read、 view后,这时将对回滚段的两种slot(m_redo、m_noredo)中的insert_undo进行清理,调用的函数为trx_undo_insert_cleanup

  1. trx_undo_insert_cleanup

trx_undo_insert_cleanup用于在事务提交或回滚后释放insert 回滚日志。

(1)、首先将undo对象从回滚段中的insert_undo_list中移除

(2)、判断undo->state是否是TRX_UNDO_CACHED状态,如果是,则将undo对象添加到insert_undo_cached链表中,以备重用;如果不是,则首先删除文件中的撤消日志段,调用的函数为trx_undo_seg_free,并且修改当前回滚段的大小(rseg->curr_size),并释放undo对象所占的内存。调用的函数为trx_undo_mem_free。

事务完成提交后,需要将其使用的回滚段引用计数rseg->trx_ref_count减1;

2、trx_flush_log_if_needed

现在,根据my.cnf选项,我们可以将日志缓冲区写入日志文件,如果操作系统不崩溃,则使事务持久。我们还可以将日志文件刷新到磁盘,从而使事务在操作系统崩溃或断电时也持久。

InnoDB的组提交中的想法是,一组事务聚集在一个trx后面,对日志文件进行物理磁盘写操作,并且当该物理写操作完成时,这些事务之一进行写操作,从而提交整个组。

注意,只有在数据库中有> 2个用户时,此组提交才会带来好处。然后,至少2个用户可以聚集在一个后面,将物理日志写入磁盘。

如果我们在prepare_commit_mutex下调用trx_commit,则将延迟可能的日志写入,并刷新到单独的函数trx_commit_complete_for_mysql,该函数仅在线程释放互斥量时才调用。

这是为了使组提交算法起作用。否则,prepare_commit互斥锁将序列化所有提交,并阻止收集一组事务。

一旦进行组提交,则调用函数trx_flush_log_if_needed。这个函数接着调用trx_flush_log_if_needed_low函数,这个函数中会根据srv_flush_log_at_trx_commit的值进行switch分支选择:

2:flush = false;   写入日志,但不要将其刷新到磁盘;

1:flush = true;   写入日志并有选择地将其刷新到磁盘;

0:return; 不做任何操作。

写入日志的调用的函数为log_write_up_to

(1)、log_write_up_to

函数log_write_up_to用于将缓冲区写入日志文件组。如果传入参数flush_to_disk为true,还将写入的日志页刷新到文件系统。

写日志文件调用log_group_write_buf函数,刷新到盘调用log_write_flush_to_disk_low函数

3、srv_active_wake_master_thread

写完日志文件并且刷新到盘后,需要告诉服务器发生了一些活动,因为事务中确实更改的某些内容,这时就需要唤醒master线程,诸如主线程,清除线程或page_cleaner线程之类的后台实用程序线程可能需要做一些工作,调用的函数为srv_active_wake_master_thread。

4、trx_roll_savepoints_free

接下来就需要释放所有的保存点(savepoints)了,保存点保存的是事务中一部分,释放保存点调用的函数为trx_roll_savepoints_free。

因为我们可以异步回滚事务,所以我们在最后一步更改了状态。一旦提交或回滚开始, trx_t :: abort便无法更改,因为在到达此处之前,我们将释放锁。如果参数trx->abort为true,则将state置为TRX_STATE_FORCED_ROLLBACK,否则设置为TRX_STATE_NOT_STARTED。

最后重新初始化事务(trx_init),释放锁(trx_mutest_exit)。

trx_commit_complete_for_mysql

innobase commit完后,判断read_only参数,如果不是只读,则会将commit_threads数减1。检测的cond_signal,调用的函数为mysql_cond_signal。

如果需要,调用trx_commit_complete_for_mysql函数将日志刷新到磁盘。这个函数的流程图如下:

函数调用trx_flush_log_if_needed,这个函数之前分析过,就不在重复。

四、调测结果:

1、-->PREPARED 或者 -->NOT_STARTED

innobase_commit函数中,首次commit过程check_trx_exists函数初始化事务的state、must_flush_log_later、commit_lsn、read_only等参数。

深入函数内部,check_trx_exists中的thd_to_trx函数会将函数初始化为PREPARED状态:

如果事务的地址为空,则会调用函数trx_create_low函数创建事务,将trx的各个参数进行初始化,其中状态初始化为NOT_STARTED。trx0trx.cc:475行trx_create_low函数中定义:

2、-->ACTIVE

开启事务后第一个语句触发,将事务设置为ACTIVE,调用的函数为:trx_start_low。

trx0trx.cc:1434行trx_start_low函数中定义:

3、-->COMMITED_IN_MEMORY

在lock_trx_release_locks函数将PREPARED状态设置为COMMITED_IN_MEMORY状态

在lock0lock.cc:6885行函数lock_trx_release_locks中定义:

实测结果:

4、-->NOT_STARTED

在commit_in_memory函数将COMMITED_IN_MEMORY状态设置为NOT_STARTED。实测结果为:

在trx0trx.cc:2086行commit_in_memory函数有定义:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2165771.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-09-25

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-09-25 1. PromSec: Prompt Optimization for Secure Generation of Functional Source Code with Large Language Models (LLMs) M Nazzal, I Khalil, A Khreishah, NH Phan - arXiv preprint arXiv:2409.12699, 2…

无人机探测:光电侦测技术详解

无人机探测技术作为现代军事、安防及民用领域的重要组成部分,其核心在于高效、精准地识别与跟踪空中目标。光电侦测技术,作为无人机探测的主要手段之一,利用光学与电子学原理,通过捕捉并分析无人机反射或发射的光信号,…

Debian与Ubuntu:深入解读两大Linux发行版的历史与联系

Debian与Ubuntu:深入解读两大Linux发行版的历史与联系 引言 在开源操作系统的领域中,Debian和Ubuntu是两款备受瞩目的Linux发行版。它们不仅在技术上有着密切的联系,而且各自的发展历程和理念也对开源社区产生了深远的影响。本文将详细介绍…

【C++指南】C++中的内存对齐规则及原因详解

💓 博客主页:倔强的石头的CSDN主页 📝Gitee主页:倔强的石头的gitee主页 ⏩ 文章专栏:《C指南》 期待您的关注 目录 引言 一、为什么要进行内存对齐 二、C中的内存对齐规则 三、内存对齐示例讲解 示例代码 运行…

Spring Boot 进阶-第一个程序HelloWorld

从我们学习编程语言开始,每次入门一个语言都是从Hello World开始,当然这里我们也不例外。首先从一个简单的HelloWorld程序开始。 既然是要学着做Java Web开发,那么首先需要了解的就是如何去编写一个RESTFul风格的接口,这里我们就需要引入一个pom的依赖。 <dependency&g…

矩阵分析 学习笔记4 内积与Gram矩阵

内积 定义 由于对称&#xff0c;第二变元线性那第一变元也线性了。例如这个&#xff1a;

【YashanDB知识库】yashandb执行包含带oracle dblink表的sql时性能差

本文内容来自YashanDB官网&#xff0c;具体内容请见https://www.yashandb.com/newsinfo/7396959.html?templateId1718516 问题现象 yashandb执行带oracle dblink表的sql性能差&#xff1a; 同样的语句&#xff0c;同样的数据&#xff0c;oracle通过dblink访问远端oracle执行…

spring学习 【基础】

目录 1.将bean交给spring容器管理 1. 编写bean对象 2. 在resources目录下创建spring config xml文件&#xff0c;并管理bean对象 3. spring容器管理工厂 2.Spring Bean 生命周期 2.1 Spring Bean初始化方法 2.1.1 自定义初始化方法 2.1.2 实现接口(InitializingBean)…

Spring6梳理12——依赖注入之注入Map集合类型属性

以上笔记来源&#xff1a; 尚硅谷Spring零基础入门到进阶&#xff0c;一套搞定spring6全套视频教程&#xff08;源码级讲解&#xff09;https://www.bilibili.com/video/BV1kR4y1b7Qc 12 依赖注入之注入Map集合类型属性 12.1 创建Student类和Teacher类 Student类中创建了run…

灵当CRM multipleUpload.php 文件上传致RCE漏洞复现

0x01 产品描述&#xff1a; 灵当CRM是一款专为中小企业量身定制的智能客户关系管理工具&#xff0c;由上海灵当信息科技有限公司开发和运营。该系统广泛应用于多个行业&#xff0c;包括金融、教育、医疗、IT服务及房地产等领域&#xff0c;旨在满足企业对客户个性化管理的需求&…

集合ArrayList常用方法

源代码&#xff1a; 输出结果&#xff1a;

小米Civi2机型工程固件 资源预览 刷写说明 与nv损坏去除电阻图示

小米Civi2机型机型代码为:ziyi。次期开始讲在博文中陆续解析机型nv损坏修复的一些步骤与当前机型去除校验电阻相关的图示与说明。注意。米系列机型有串码校验。去除电阻需要一定的技术,请谨慎操作。 通过博文了解 1💝💝💝-----此机型工程固件的资源刷写注意事项 2�…

【计算机网络 - 基础问题】每日 3 题(二十四)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏&…

音乐服务器测试报告

项目背景 该音乐服务器系统使用的是前后端分离的方式来实现,将相关数据存储到数据库中, 且将其部署到云服务器上. 前端主要构成部分有: 登录页面, 列表页面, 喜欢页面, 添加歌曲4个页面组成. 通过结合后端实现了主要的功能: 登录, 播放音乐, 添加音乐, 收藏音乐, 删除音乐, 删…

uniapp实现在表单中展示多个选项,并且用户可以选择其中的一个或多个选项

前言 uni-data-checkbox是uni-app的一个组件,用于在表单中展示多个选项,并且用户可以选择其中的一个或多个选项。该组件可以通过设置不同的参数来控制选项的样式、布局和行为。 提示:以下是本篇文章正文内容,下面案例可供参考 uni-data-checkbox组件具有以下特点:: 1、跨…

Git 工作区、暂存区与修改全解析

工作区和暂存区是 Git 中一个非常重要的概念&#xff0c;弄明白了他们&#xff0c;就弄明白了 Git 的很多操作到底干了什么。 ‍ 工作区&#xff08;Working Directory&#xff09; 工作区&#xff0c;就是一个目录&#xff0c;比如我的 LearnGit ​文件夹就是一个工作区&am…

JavaScript --模版字符串用反引号

用反引号 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-width, i…

SpringBoot集成阿里easyexcel(一)基础导入导出

easyexcel主要用于excel文件的读写&#xff0c;可使用model实体类来定义文件读写的模板&#xff0c;对开发人员来说实现简单Excel文件的读写很便捷。可参考官方文档 https://github.com/alibaba/easyexcel 一、引入依赖 <!-- 阿里开源EXCEL --><dependency><gr…

《深度学习》ResNet残差网络、BN批处理层 结构、原理详解

目录 一、关于ResNet 1、什么是ResNet 2、传统卷积神经网络存在的问题 1&#xff09;梯度消失和梯度爆炸问题 2&#xff09;训练困难 3&#xff09;特征表示能力受限 4&#xff09;模型复杂度和计算负担 3、如何解决 1&#xff09;解决梯度问题 BN层重要步骤&#xff1a; 2…

Gstreamer中,使用mp4或者flv作为视频源去推流RTP等视频流时,需要先解码在编码才能正常

前言&#xff1a; 在Gstreamer中&#xff0c;视频源可以有很多&#xff0c;在很多时候&#xff0c;我们为了测试&#xff0c;会使用MP4等文件作为视频源进行测试&#xff0c;但是发现无论是我自己测试&#xff0c;还是很多网上的命令&#xff0c;都需要先对mp4的h264数据解码以…