MySQL 核心模块揭秘 | 04 期 | 终于要启动事务了

做了那么多准备工作,终于要启动 InnoDB 事务了。

作者:操盛春,爱可生技术专家,公众号『一树一溪』作者,专注于研究 MySQL 和 OceanBase 源码。

爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编并注明来源。
本文基于 MySQL 8.0.32 源码,存储引擎为 InnoDB。

1. 启动事务

在《BEGIN 语句会马上启动事务吗?》这篇文章中,我们介绍过开始一个事务的 8 种 SQL 语句:

SQL 复制代码
/* 1 */ BEGIN
/* 2 */ BEGIN WORK
/* 3 */ START TRANSACTION
/* 4 */ START TRANSACTION READ WRITE
/* 5 */ START TRANSACTION READ ONLY
/* 6 */ START TRANSACTION WITH CONSISTENT SNAPSHOT
/* 7 */ START TRANSACTION WITH CONSISTENT SNAPSHOT, READ WRITE
/* 8 */ START TRANSACTION WITH CONSISTENT SNAPSHOT, READ ONLY

语句 1 ~ 5 都不会马上启动新事务,只会给执行这些语句的线程打上 OPTION_BEGIN 标记,在这之后执行第一条 SQL 时,才会真正的启动事务。

在《我是一个事务,请给我一个对象》这篇文章中,我们介绍过:InnoDB 给事务分配一个对象(trx)之后,该对象的状态属性(state)值为 TRX_STATE_NOT_STARTED,表示事务还未开始。

启动事务最重要的事情之一,就是修改事务状态了,代码是这样的:

CPP 复制代码
trx->state.store(TRX_STATE_ACTIVE, std::memory_order_relaxed)

事务状态从 TRX_STATE_NOT_STARTED 修改为 TRX_STATE_ACTIVE,表示事务已经启动,是个活跃事务了。

我们执行 show engine innodb status 可能会看到类似下面的内容:

SQL 复制代码
LIST OF TRANSACTIONS FOR EACH SESSION:
0 lock struct(s), heap size 1192, 0 row lock(s)
---TRANSACTION 206242, ACTIVE 42 sec

其中,ACTIVE 就来源于事务的 TRX_STATE_ACTIVE 状态。

2. 读事务

事务启动于执行第一条 SQL 语句时,如果第一条 SQL 语句是 select、update、delete,InnoDB 会以读事务的身份启动新事务。

读事务的 ID 会被设置为 0:

CPP 复制代码
trx->id = 0;

对于 ID 等于 0 的事务,查询 information_schema.innodb_trx 表得到的 trx_id 字段值并不是 0,而是一串比较长的数字:

SQL 复制代码
************[ 1. row ]************
trx_id      | 281480261177256
trx_state   | RUNNING
trx_started | 2023-12-24 22:39:45
...

上面的 trx_id 字段值是这样计算出来的:

  • 把事务对象的内存地址转换为十进制数字。
  • 用上一步得到的数字加上 281474976710656。这个数字是 6 字节能够存放的最大事务 ID + 1 ,6 字节是记录中隐藏的事务 ID 字段(DB_TRX_ID)占用的字节数。
  • 经过以上两步计算,就得到了 trx_id 字段值。

以上面查询出来的事务为例,事务对象的内存地址为 0x000000013afa8fa8。内存地址以 0x 开头,是十六进制,转换为十进制得到 5284466600,再加上 281474976710656 就得到了 trx_id 字段值 281480261177256

通过这个计算逻辑,我们可以根据 information_schema.innodb_trx 表中 trx_id 字段值判断事务是否分配了 ID:

  • 如果 trx_id 字段值大于等于 281474976710656,说明该事务没有分配 ID。
  • 如果 trx_id 字段值小于 281474976710656,说明该事务分配了 ID。

3. 只读事务

只读事务是读事务的一个特例,从字面上看,它是不能改变(插入、修改、删除)表中数据的。

然而,这个只读并不是绝对的,只读事务不能改变系统表、用户普通表的数据,但是可以改变用户临时表的数据。

作为读事务的特例,只读事务也要遵守读事务的规则,事务 ID 应该为 0。

只读事务操作系统表、用户普通表,只能读取表中数据,事务 ID 为 0(即不分配事务 ID)没问题。

只读事务操作用户临时表,可以改变表中数据,而用户临时表也支持事务 ACID 特性中的 3 个(ACI),这就需要分配事务 ID 了。

如果只读事务执行的第一条 SQL 语句就是插入记录到用户临时表的 insert,事务启动过程中会分配事务 ID。我们可以通过一个例子来确认这一点:

SQL 复制代码
-- 开始只读事务之前创建一个用户临时表
-- 因为只读事务里不能创建用户临时表(会报错)
create temporary table t_tmp (
	id int unsigned auto_increment primary key,
  i1 int not null default 0,
  i2 int not null default 0
) engine = InnoDB default charset utf8;

-- 标识要开启一个只读事务
start transaction read only;

-- 往用户临时表中插入一条记录
insert into t_tmp(i1, i2) values (10, 100);

查询 information_schema.innodb_trx 表可以看到只读事务分配了事务 ID:

SQL 复制代码
select * from information_schema.innodb_trx\G

************[ 1. row ]************
trx_id      | 206266
trx_state   | RUNNING
trx_started | 2023-12-24 21:44:51
...

trx_id 字段值 206266 小于 281474976710656,说明这个只读事务分配了事务 ID。

4. 读写事务

如果事务执行的第一条 SQL 语句是 insert,这个事务就会以读写事务的身份启动。

读写事务的启动过程,主要会做这几件事:

  • 为用户普通表分配回滚段,用于写 Undo 日志。
  • 分配事务 ID。
  • 把事务对象加入 trx_sys->rw_trx_list 链表。这个链表记录了所有读写事务。
CPP 复制代码
UT_LIST_ADD_FIRST(trx_sys->rw_trx_list, trx);

5. 内部事务

用户事务以什么身份启动,取决于执行的第一条 SQL 是什么。

和用户事务不一样,InnoDB 启动内部事务都是为了改变表中数据,所以,内部事务都是读写事务

作为读写事务,所有内部事务都会加入到 trx_sys->rw_trx_list 链表中。

6. 总结

InnoDB 开启内部事务,是为了改变表中数据,所以,内部事务都以读写事务的身份启动。

用户事务可能会读取、改变表中数据,根据执行的第一条 SQL 语句不同,以不同身份启动:

  • 执行的第一条 SQL 语句是 select、update、delete,以读事务身份启动事务。
  • 执行的第一条 SQL 语句是 insert,以读写事务 身份启动事务。
    如果只读事务执行的第一条 SQL 语句是插入记录到用户临时表的 insert,也会分配事务 ID。

本期问题:mysql_trx_list、rw_trx_list 这两个链表分别用来干什么?欢迎留言交流。
下期预告:MySQL 核心模块揭秘 | 05 期 | 读事务和只读事务的变形记。

更多技术文章,请访问:opensource.actionsky.com/

关于 SQLE

SQLE 是一款全方位的 SQL 质量管理平台,覆盖开发至生产环境的 SQL 审核和管理。支持主流的开源、商业、国产数据库,为开发和运维提供流程自动化能力,提升上线效率,提高数据质量。

相关推荐
飞翔沫沫情几秒前
《快速部署Mysql-slave 容器,实现高效主从同步》
数据库·docker·mysql主从同步
SelectDB技术团队2 分钟前
一文了解多云原生的现代化实时数仓 SelectDB Cloud
大数据·数据库·数据仓库·云原生·云计算
木卫二号Coding1 小时前
docker-开源nocodb,使用已有数据库
数据库·docker·开源
StarRocks_labs1 小时前
StarRocks 存算分离在得物的降本增效实践
数据库·数据仓库·湖仓
敲代码敲到头发茂密2 小时前
基于 LangChain 实现数据库问答机器人
数据库·人工智能·语言模型·langchain·机器人
一入程序无退路2 小时前
c语言传参数路径太长,导致无法获取参数
linux·c语言·数据库
陌夏微秋3 小时前
STM32单片机芯片与内部47 STM32 CAN内部架构 介绍
数据库·stm32·单片机·嵌入式硬件·架构·信息与通信
计算机学无涯4 小时前
Spring事务回滚
数据库·sql·spring
web130933203984 小时前
flume对kafka中数据的导入导出、datax对mysql数据库数据的抽取
数据库·kafka·flume
张声录14 小时前
【ETCD】【实操篇(二十)】浅谈etcd集群管理的艺术:从两阶段配置到灾难恢复的设计原则
数据库·etcd