Files
rikako-note/mysql/mysql文档/mysql_事务.md
2025-07-23 01:16:12 +08:00

23 KiB
Raw Blame History

事务

事务分类

事务通常可分为如下类型:

  • 扁平事务(flat transaction)
  • 带保存点的扁平事务(flat transaction with savepoints)
  • 链事务(chained transaction)
  • 嵌套事务(nested transaction)
  • 分布式事务(distributed transaction)

扁平事务

扁平事务为最常用的事务,在扁平事务中,所有操作都处于统一层次,其由begin work开始,并由commit workrollback work结束。

扁平事务的操作是原子的,要么都提交,要么都回滚。

带有保存点的扁平事务

对于带有保存点的扁平事务,其支持在事务执行过程中回滚到同一事务中较早的一个状态

在事务执行过程中,可能并不希望所有的操作都回滚,放弃所有操作的代价可能太大。通过保存点,可以记住事务当前的状态,在后续发生错误后,事务能够回到保存点当时的状态。

相较于扁平事务只能够全部回滚,带保存点的扁平事务能够回滚到保存点时的状态。

保存点可以通过save work来创建,使用示例如下所示

链事务

可视为保存点事务的一个变种。在使用带保存点的事务时,如果系统发生崩溃,那么所有的保存点都会消失。在后续重启进行恢复时,事务需要从开始处重新执行,而不是从最近的一个保存点开始执行。

若事务在数据库未提交时发生崩溃那么在数据库再次重启执行recovery操作时会对未提交的事务进行回滚即使之前事务存在保存点也会全部回滚

链事务的思想是,当提交事务时,释放不必要的数据对象,将必要的上下文隐式传递给下一个要开始的事务。提交事务和开始下一个事务操作必须为原子操作,下一个事务必须徐要能看到上一个事务的结果,示例如下:

和带保存点的扁平事务不同的是,带保存点的扁平事务能够回滚到任意正确的保存点,而链事务只能回滚当前事务。

且链事务和带保存点的扁平事务,对于锁的处理也不同:

  • 链事务对于每个事务commit后释放持有的锁
  • 带保存点的扁平事务:在整个事务提交前,不会释放持有的锁

嵌套事务

嵌套事务为一个层次结构框架,由顶层事务控制各个层次的事务。嵌套在顶层事务中的事务被称为子事务

如下为Moss理论嵌套事务的定义

  • 嵌套事务是由若干事务组成的一颗树,子树既可以是嵌套事务,又可以是扁平事务
  • 处在叶子节点的事务是扁平事务
  • 位于根节点的事务被称为顶层事务,其他事务被称为子事务,事务的predecessor被称为父事务,事务的下一层事务被称为子事务
  • 子事务既可以提交又可以回滚,但是子事务的提交并不会立马生效,除非其父事务已经被提交。任何子事务都在顶层事务提交后才真正提交
  • 树中任何一个事务的回滚会引其所有子事务都一起回滚

在Moss理论中实际工作被交由叶子节点来完成只有叶子节点的事务才能够访问数据库,发送消息,获取其他类型的资源高层事务仅仅负责逻辑控制,即负责何时调用相关子事务

即使一个系统不支持嵌套事务,也可以通过保存点技术来模拟嵌套事务。

savepoint和嵌套事务区别

在使用保存点来模拟嵌套事务时,在锁持有方面和嵌套事务有差别。

  • 嵌套事务:在使用嵌套事务时,不同子事务在数据库持有的锁不同
  • 保存点:在通过保存点来模拟嵌套事务时,用户无法选择哪些锁被哪些子事务继承,无论有多少个保存点,所有的锁都可以得到访问

分布式事务

通常是在分布式环境运行的扁平事务,需要访问网络中的不同节点。

分布式事务同样需要满足ACID的特性要么都发生要么都不发生。

对于innodb存储引擎其支持扁平事务、带有保存点的扁平事务、链事务、分布式事务。innodb并不原生支持嵌套事务单可以通过带保存点的事务来模拟串行的嵌套事务

事务实现

对于事务的ACID特性其实现如下

  • I(隔离性):事务隔离性通过锁来实现
  • A(原子性), D(持久性)事务的原子性和持久性可以通过redo log来实现
  • C(一致性):事务一致性通过undo log来实现

redo

redo log重做日志用于实现事务的持久性其由两部分组成

  • 内存中的重做日志缓冲redo log buffer
  • 磁盘中的重做日志文件redo log file

redo log persists before transaction committed

innodb存储引擎支持事务其通过force log at commit机制实现事务的持久性,即当事务提交时,必须先将该事务的所有日志写入到磁盘的日志文件中进行持久化,直到该过程完成后事务才提交完成。

上述描述中提交时将事务所有日志写入到磁盘的日志文件中,这句话中日志代表redo log 和 undo log

  • redo log用于保证事务持久性
  • undo log用于帮助事务回滚也用于mvcc功能

redo log和undo log比较

  • redo log在mysql server运行时只会被顺序的写入server运行时并不会被读取
  • undo log则不同在server运行时可能需要对事务进行回滚或执行mvcc操作此时可能需要对undo log文件进行随机读写

redo log和binlog的比较

binglog通常用于对数据库进行point in time形式的恢复从某个时间点起恢复数据以及主从复制但是binlog和redo log的差别如下

  • binlog针对的是mysql数据库级别不止用于innodb还用于其他存储引擎
  • redo log属于innodb存储引擎级别
  • binlog实际记录的是对应sql语句属于逻辑日志
  • redo log实际记录的格式则是物理格式具体为针对某个页面的物理修改

redo log和 bin log日志写入磁盘的时机也有所不同

  • bin log仅在事务提交后才进行一次写入
  • redo log在事务执行过程中也可能发生写入redo log buffer满后会写入到磁盘中故而redo log file中同一事务写入的redo log内容可能并非是连续的多个事务在写入redo log时可能会交叉写入

innodb_flush_log_at_trx_commit

参数innodb_flush_log_at_trx_commit用于控制redo log/ undo log刷新到磁盘的策略该参数默认值为1,即事务提交时刷新日志到磁盘。除了默认值之外,还可以为该参数设置如下值:

  • 0 事务提交时不刷新日志到磁盘仅在master thread中刷新日志到磁盘master thread中刷新操作每秒触发一次
  • 2事务提交时刷新日志但是仅将日志写入到文件系统的缓存中并不进行fsync操作

innodb_flush_log_at_trx_commit参数设置为02虽然可以在一定幅度上提高性能但是会丧失数据库的ACID特性。

log block

在innodb中redo都是以512字节的大小为单位进行存储的即redo log buffer、redo log file都是以block的形式进行保存block的大小为512字节。

block & atomic

若针对相同的页redo log的大小大于512字节那么其会被分割为多个block进行存储。且由于redo log block的大小和磁盘扇区相同故而在将block时无需使用double write机制针对特定的block起写入为原子的要么写入成功要么写入失败不会像页page一样存在dirty的情况。

redo log block中包含的内容除了日志本身外还包含log block headerlog block tailer内容。log block header + log block content + log block tailer合计占用512字节其中各部分大小如下

  • log block header: 12字节大小
  • log block content: 492字节大小
  • log block tailer: 8字节大小

如上所示每个redo log block可实际存储的内容大小为492字节。

log block header

log block header大小为12字节由如下部分组成

  • LOG_BLOCK_HDR_NO: 占用4字节
  • LOG_BLOCK_HDR_DATA_LEN: 占用2字节
  • LOG_BLOCK_FIRST_REC_GROUP: 占用2字节
  • LOG_BLOCK_CHECKPOINT_NO: 占用4字节
LOG_BLOCK_HDR_NO

log buffer由log block所组成可以将log buffer看作是log block的数组故而log block header中LOG_BLOCK_HDR_NO起代表当前block在buffer中的位置。

LOG_BLOCK_HDR_NO由于表示的是log buffer中的数组小标故而可知LOG_BLOCK_HDR_NO其是递增的,并且可以循环使用。LOG_BLOCK_HDR_NO的大小为4字节但是其首位用作flush bit,故而,其可表示的最大长度为2^31 bytes = 2GiB

LOG_BLOCK_HDR_DATA_LEN

LOG_BLOCK_HDR_DATA_LEN大小为2字节代表log block所占用的大小当log block被写满时该值为0x200表示当前log block使用完block中所有的可用空间即log block的大小为512字节。

LOG_BLOCK_FIRST_REC_GROUP

LOG_BLOCK_FIRST_REC_GROUP占用2个字节表示log block中第一个日志所处的偏移量。

LOG_BLOCK-FIRST_REC_GROUP的取值可能存在如下场景:

  • 该值大小和LOG_BLOCK_HDR_DATA_LEN相同则代表当前block中不包含新的日志即当前block中存储的存储的全是上一block中record的后续部分

下图表示事务T1的重做日志占用762字节事务T2的重做日志占用100字节的场景。

由于每个block中最多只能保存492字节的数据故而T1事务的762字节需要分布在两个block中第一个block保存492字节的数据第二个block中保存剩余270字节的数据。

  • 其中左侧的blockLOG_BLOCK_FIRST_REC_GROUP值为12代表第一个record开始的位置紧接在log block header之后
  • 而右侧的blockLOG_BLOCK_FIRST_REC_GROUP的值为12 + 270 = 282 bytes。在存放第一条record之前不仅有log block header对应的12字节还有之前T1剩余日志的270字节
LOG_BLOCK_CHECKPOINT_NO

LOG_BLOCK_CHECKPOINT_NO占用4字节大小代表log block最后被写入时的检查点第四字节的值。

LSNlog sequence number为一个全局唯一且单调递增的64位数字当发生数据修改时redo log内容会增加此时LSN也会增加

CHECKPOINT则是一个LSN值同样为64位整数代表位于CHECKPOINT之前所有的修改已经被持久化到数据库中,位于CHECKPOINT之前的redo log内容可以被安全的覆盖

log block tailer

log block tailer中仅由一个部分组成LOG_BLOCK_TRL_NO,其值和LOG_BLOCK_HDR_NO相同。

log group

log group被称为重做日志组其中包含多个redo log文件innodb中只有一个log group。

log group只是一个逻辑上的概念由多个redo log file组成。log group中每个redo log file大小相同。redo log file中存储的是redo log block在innodb引擎运行过程中会将redo log buffer中的log block刷新到磁盘文件中。

redo log buffer刷新到磁盘中的时机

redo log buffer会在如下时机将log block刷新到磁盘中

  • 事务提交时
  • log buffer中有一半的内存空间已经被使用时
  • log checkpoint时(checkpoint时会导致脏页被刷新到磁盘上而WAL要求脏页刷新前刷新redo log buffer)
WAL

write-ahead logging是一种为database系统提供原子性持久性的技术。

write ahead logappend-only的辅助磁盘存储结构用于crash recovery和transaction recovery。

在使用WAL的系统中在所有的changes被应用到数据库之前要求changes都被写入到log中。

所以在innodb中脏页被刷新到磁盘之前脏页对应的newest_lsn之前的redo log都必须被刷新到磁盘中。redo log file中最新的lsn必须大于磁盘页文件中最大的lsn

在redo log buffer中的log block刷新到redo log file中时其会追加append到redo log file的末尾。当redo log group中的一个文件被写满时其会接着写入下一个redo log file其行为称为round-robin

在redo log group中的每个redo log file中其前2KB4个log block大小均不用于存储log block前2KB内容如下

  • 对于log group中的第一个redo log file前2KB用于存储如下内容下列每个部分大小均为一个block
    • log file header
    • checkpoint 1
    • checkpoint2
  • 对于log group中非第一个redo log file其仅保留开头2KB的空间但并不保存信息

redo log format

innodb中存储管理是基于页的故而redo log format格式也基于页。

redo log头部格式

redo log头部格式通常包含3部分

  • redo_log_type 重做日志类型
  • space 表空间id
  • page_no 页的偏移量

之后redo log body部分根据重做日志类型的不同会存储不同内容。

LSN

LSN代表的是日志序列号其大小为8字节且单调递增。LSN代表事务写入redo log的总字节数

页LSN

在每个页的头部,都存在FIL_PAGE_LSN其记录了该页的lsn。在页中LSN代表该页最后刷新时的lsn大小。

FIL_PAGE_LSN在buffer pool pagedisk page中均存在,二者记录的值不同:

  • buffer pool page header中FIL_PAGE_LSN记录的是内存页最后被修改的LSN
  • disk page header中FIL_PAGE_LSN记录的是最后被刷新到磁盘的页对应的最大修改LSN

在执行crash recovery过程中会从CHECKPOINT开始一直到redo log file末尾逐条处理redo log record对于每条redo log record关联的页会比较record_lsnFIL_PAGE_LSN的大小:

  • record_lsn <= FIL_PAGE_LSN代表当前redo record对应的修改已经包含在页中当前redo log record直接跳过即可
  • record_lsn > FIL_PAGE_LSN代表当前redo record中的修改不存在于页中需要对页应用record修改并在修改完后更新页的FIL_PAGE_LSN
查看LSN

可以通过show engine innodb status来查看LSN情况核心数值如下

  • log sequence number代表当前LSN
  • log flushed up to 代表已经刷新到磁盘文件中的LSN
  • last checkpoint at: 代表页已经刷新到磁盘的LSN

recovery

innodb在启动时不管上次数据库运行是否正常关闭,都会尝试执行恢复

redo log是物理日志故而恢复速度相较逻辑日志要快得多,恢复操作仅需从checkpoint开始。

例如,对于如下数据表

create table t (
    a int,
    b int,
    primary key(a),
    key(b)
);

若执行sql语句

insert into t select 1,2;

在执行时,需要修改如下内容:

  • 聚簇索引页(包含数据)
  • 辅助索引页

故而,其记录重做日志内容大致为

page(2,3), offset 32, value 1,2; # 聚簇索引页
page(2,4), offset 64, value 2; # 辅助索引页

由上述示例可知redo log为物理日志记录的是对页的物理修改故而redo log是幂等的

undo

redo log记录了对页的物理操作可以用于进行redo。而undo和redo不同undo主要用于对事务的回滚。

undo的存放位置和redo不同

  • redo log存放在redo log file中
  • undo log存放在数据库内部的segment中该segment被称为undo segmentundo段位于共享的表空间内

undo log和redo log差异

  • redo log为物理日志记录的是对页的修改而undo log则是逻辑日志对每个insert操作undo log会生成一个相反的delete对update也会生成另一个逆向的update
  • redo log是全局的innodb中所有事务都会向同一个redo log交叉写入而undo log则是针对事务的每个事务都有其自己的undo log chain

非锁定读

除了用于事务回滚外undo log还可以用于MVCC。当事务A尝试读取一条记录R时如果记录R已经被另一个事务B占用那么事务A可以通过undo log读取行数据之前的版本。

上述实现被称为非锁定读

undo log的产生会伴随redo log的产生

undo log其本质仍然是数据。undo log其存放在表空间的undo segment中仍然可被可做是数据WAL(write-ahead logging)要求变更被应用到数据库之前,需要先写入日志

故而在生成undo log时对于undo页的修改也会被记录到redo log中。

存储管理

innodb通过segment来管理undo log其管理方式如下

  • innodb包含rollback segment
  • 每个rollback segment会记录1024个undo log segment
  • undo log segment中会进行undo页的申请
  • 共享表空间偏移量为5的页会记录所有rollback segment header所在的页
    • 偏移量为5的页类型为FIL_PAGE_TYPE_SYS
innodb_undo_directory

该参数用于设置rollback segment文件所在的路径默认为./,代表datadir

如果innodb_undo_directory变量没有被定义那么undo tablespace将会被创建再datadir下。默认情况下undo tablespaces文件的名称为undo_001undo_002

innodb_rollback_segments

每个undo tablespace支持最大128个rollback segmentsinnodb_rollback_segments变量定义了rolback segments的数量。

每个rollback segments支持的事务数量由rollback segment中undo slot的数量每个事务需要的undo log数量来决定。

当innodb页大小为16KB时rollback segment中undo slot的数量为innodb page size/ 16即1024个。

易知每个slot占用的大小为16bit即长度为2字节。

故而,slot中存放的是指向undo segment的句柄而不是存放undo segment本身

innodb_undo_tablespaces

该变量设置了undo tablespaces的数量。

purge

在事务提交之后并不能立刻删除undo log以及undo log所在的页其他事务仍有可能通过undo log来还原数据行的之前版本。故而在事务提交时会将undo log放入到一个链表中交由purge线程来决定是否最终删除undo log以及undo log所在的页。

purge代表`清空不再被需要的旧版本数据行及其对应的undo log记录。

如果为每一个事务分配一个单独的undo页那么会非常浪费存储空间。由于事务提交时所分配的undo页并不能立刻释放故而当数据库负载较大时可能同时存在大量的undo页会占用相当多的存储空间。

undo页的重用设计

在innodb对undo页的设计中考虑了对undo页的重用。当事务提交时首先会将undo log放在链表中然后判断undo页的使用空间是否小于3/4。如果是代表该undo页可以被重用之后新的undo log会记录在当前undo log的后面。

即undo page是可被重用的当事务提交时如果undo log的使用空间小于3/4那么该undo页是可以被重用的一个undo页中可能包含多个undo log

核心概念

rollback segment

undo tablespace由rollback segment构成每个undo tablespace最多支持128个rollback segmentinnodb_rollback_segments定义了rollback segments的数量。

undo slots

undo slot是rollback segment内的slot由rollback segment进行管理。

undo slot主要用于关联undo segment当事务启动时系统会从rollback segment中获取一个空闲的undo slot成功获取undo slot后即代表关联了一个undo segment

每个undo log slot中会存储一个page_no其指向undo log segment的起始页位置。

undo segment

undo segment为undo slot指向的空间undo segment中包含多个undo pages而undo segment中则包含了undo log。

undo log格式

在innodb存储引擎中undo log分为如下两种类型

  • insert undo log:
    • insert undo log是事务在insert操作中产生的undo log
    • 在事务提交之前事务插入的数据对其他事务不可见而事务提交之后事务插入的数据对其他读已提交的事务才可见故而insert undo log在事务提交之后不再被需要因为在读已提交隔离级别下insert undo log是可见的在可重复读的隔离级别下insert undo log则是不可见的没有中间版本只能可见/不可见
    • 故而当事务提交之后即可删除该事务关联的insert undo log
  • update undo log
insert undo log

在事务提交之后insert undo log即可被删除故而无需purge操作。

insert undo log结构如下

  • next下一个undo log的位置长度为2字节
  • type_cmpl: 记录undo的类型对于insert undo来说该值为11。该字段长度为1字节
  • undo_no记录事务的id压缩后保存
  • table_id: 记录undo log对应的表对象压缩后保存
  • 记录所有主键的列和值(本次插入的数据,可能多条)
  • start位于undo log尾部记录undo log的开始位置长度为2字节
rollback

在执行rollback操作时可以根据insert undo log中存储的table id主键列、主键值来定位需要回滚的行数据直接删除回滚数据即可。

mysql redo log 事务大_MySQL事务实现及Redo Log和Undo Log详解-CSDN博客
update undo log

update undo log针对的是deleteupdate操作。在mvcc机制的实现中需要用到该undo log故而update undo log在事务提交后不能立刻删除

update undo log的格式如下图所示。

MySQL的redo log、undo log、binlog_mysql redolog undolog binlog-CSDN博客

对于update undo log其哥字段含义如下

  • type_cmpl: 对update undo logtype_cmpl可能的取值如下:
    • 12TRX_UNDO_UPD_EXIST_REC更新未被标记为delete的记录
    • 13TRX_UNDO_UPD_DEL_REC更新已经被标记为delete的记录
    • 14TRX_UNDO_DEL_MARK_REC将记录标记为delete
  • update vector: update vector中记录了update操作导致发生改变的列每个被修改的列信息都记录在undo log中。

undo log中主要存储旧的col值用于在回滚或mvcc时为undo操作提供信息还原数据先前的版本。