MySQL 核心模块揭秘 | 12 期 | 创建 savepoint
本文基于 MySQL 8.0.32 源码,存储引擎为 InnoDB。
目录
1. undo 日志序号
2. savepoint 结构
3. 查找同名 savepoint
4. 删除同名 savepoint
5. 保存 savepoint
6. 总结
正文
1. undo 日志序号
InnoDB 的事务对象有一个名为 undo_no
的属性。事务每次改变(插入、更新、删除)某个表的一条记录,都会产生一条 undo 日志。这条 undo 日志中会存储它自己的序号。这个序号就来源于事务对象的 undo_no 属性。
也就是说,事务对象的 undo_no 属性中保存着事务改变(插入、更新、删除)某个表中下一条记录产生的 undo 日志的序号。
每个事务都维护着各自独立的 undo 日志序号,和其它事务无关。
每个事务的 undo 日志序号都从 0 开始。事务产生的第 1 条 undo 日志的序号为 0,第 2 条 undo 日志的序号为 1,依此类推。
InnoDB 的 savepoint 结构中会保存创建 savepoint 时事务对象的 undo_no 属性值。
2. savepoint 结构
我们通过 SQL 语句创建一个 savepoint 时,server 层、binlog、InnoDB 会各自创建用于保存 savepoint 信息的结构。
server 层的 savepoint 结构是一个 SAVEPOINT
类型的对象,主要属性如下:
- prev:指向 server 层的 savepoint 链表中,上一次创建的 SAVEPOINT 对象。
- name:savepoint 的名字。
- mdl_savepoint:创建这个 savepoint 之前,事务加了哪些 MDL 锁。
binlog 的 savepoint 结构很简单,是一个 8 字节的整数。这个整数的值,是创建 savepoint 时事务已经产生的 binlog 日志的字节数,也是接下来新产生的 binlog 日志写入 trx_cache 的 offset。
为了方便介绍,我们把这个整数值称为 binlog offset。
InnoDB 的 savepoint 结构是一个 trx_named_savept_t
类型的对象,主要属性如下:
- name:InnoDB 的 savepoint 名字。这个名字是 InnoDB 自己生成的,和 server 层的 SAVEPOINT 对象中保存的 savepoint 名字不一样。
- savept:也是一个对象,类型为
trx_savept_t
,里面保存着创建 savepoint 时,事务对象的 undo_no 属性值。 - trx_savepoints:InnoDB 中多个
trx_named_savept_t
对象形成的链表。
创建 savepoint 时,server 层会分配一块 96 字节的内存,除了存放它自己的 SAVEPOINT
对象,还会存放 binlog offset
和 InnoDB 的 trx_named_savept_t
对象。
server 层的 SAVEPOINT 对象占用这块内存的前 48 字节,InnoDB 的 trx_named_savept_t 对象占用中间的 40 字节,binlog offset 占用最后的 8 字节。
3. 查找同名 savepoint
客户端连接到 MySQL 之后,MySQL 会分配一个专门用于该连接的用户线程。
用户线程中有一个 m_savepoints
链表,用户创建的多个 savepoint 通过 prev 属性形成链表,m_savepoints 就指向最新创建的 savepoint。
server 层创建 savepoint 之前,会按照创建时间从新到老,逐个查看链表中是否存在和本次创建的 savepoint 同名的 savepoint。
4. 删除同名 savepoint
如果在用户线程的 m_savepoints 链表中找到了和本次创建的 savepoint 同名的 savepoint,需要先删除 m_savepoints 链表中的同名 savepoint。
找到的同名 savepoint,是 server 层的 SAVEPOINT
对象,它后面的内存区域分别保存着 InnoDB 的 trx_named_savept_t 对象、binlog offset。
binlog 是个老实孩子,乖乖的把 binlog offset 写入了 server 层为它分配的内存里。删除同名 savepoint 时,不需要单独处理 binlog offset。
InnoDB 就不老实了,虽然 server 层也为 InnoDB 的 trx_named_savept_t 对象分配了内存,但是 InnoDB 并没有往里面写入内容。
事务执行过程中,用户每次创建一个 savepoint,InnoDB 都会创建一个对应的 trx_named_savept_t 对象,并加入 InnoDB 事务对象的 trx_savepoints 链表的末尾。
因为 InnoDB 自己维护了一个存放 savepoint 结构的链表,server 层删除同名 savepoint 时,InnoDB 需要找到这个链表中对应的 savepoint 结构并删除,流程如下:
- server 层把同名 savepoint 的
SAVEPOINT
对象后面分配给 trx_named_savept_t 对象的内存地址传给 InnoDB。 - InnoDB 根据自己的算法把内存地址转换为字符串,作为 InnoDB 的 savepoint 名字,到事务对象的
trx_savepoints
链表中找到对应的 trx_named_savept_t 对象,并从链表中删除该对象。
InnoDB 从事务对象的 trx_savepoints 链表中删除 trx_named_savept_t 对象之后,server 层接着从用户线程的 m_savepoints 链表中删除 server 层的 SAVEPOINT
对象,也就连带着清理了 binlog offset
。
5. 保存 savepoint
处理完查找、删除同名 savepoint 之后,server 层就正式开始创建 savepoint 了,这个过程分为 3 步。
第 1 步,binlog 会生成一个 Query_log_event。
以创建名为 test_savept
的 savepoint 为例,这个 event 的内容如下:
SAVEPOINT `test_savept`<br>