MySQL深入学习 --- 事务和三大日志详解

文章目录

  • 十一、MySQL事务
    • 11.1 ACID
    • 11.2 事务的状态
    • 11.3 如何使用事务
      • 3.1 显式事务
      • 3.2 隐式事务
    • 11.4 事务隔离级别
      • 4.1 数据并发问题
      • 4.2 事务隔离级别
      • 4.3 小结
  • 十二、MySQL三大日志
    • 12.1 binlog
      • 1.1 配置文件参数
    • 12.2 redo日志
      • 2.1 为什么要用redo日志
      • 2.2 redo 日志格式
      • 2.3 Mini-Transaction
      • 2.4 redo日志写入的过程
        • 2.4.1 redo log block 重做日志块
        • 2.4.2 redo log buffer 重做日志缓冲区
        • 2.4.3 redo 日志写入log buffer
      • 2.5 redo 日志文件
        • 2.5.1 redo 日志刷盘时机
        • 2.5.2 日志文件组
        • 2.5.3 checkpoint
        • 2.5.4 崩溃恢复
    • 12.3 undo日志
      • 3.1 为什么需要undo日志
      • 3.2 事务id
        • 3.2.1 分配事务id的时机
        • 3.2.2 事务id怎么生成的
      • 3.3 undo日志格式
        • 3.3.1 insert undo log
        • 3.3.2 update undo log
          • 1.Delete 操作对应的日志
          • 2.Update 操作对应的日志
            • 2.1 不更新主键
            • 2.2 更新主键
      • 3.4 FIL_PAGE_ UNDO_LOG页面
      • 3.5 Undo页面链表
      • 3.6 undo日志写入过程
        • 6.1 段的概念
        • 6.2 回滚段undo log segment
          • Undo Log Segment Header
          • Undo页面链表
          • 回滚段概念
        • 6.3 为事务分配Undo页面链表的过程
      • 3.7 undo日志在崩溃恢复的作用
    • 12.4 日志总结

往期:

  • MySQL深入学习 — mysql逻辑架构,SQL的执行流程,数据库缓冲池
  • MySQL深入学习 — MySQL存储引擎,InnoDB、MyISAM索引的数据结构
  • MySQL深入学习 — 索引的创建和删除,索引设计原则,索引失效场景,查询优化,索引下推ICP

十一、MySQL事务

MySQL中支持事务的引擎有:innodbbdb,主要用的引擎为innodb,接下来介绍innodb事务

11.1 ACID

回忆一下数据库原理的ACID原则

  • Atomicity 原子性
    • 原子性是指事务是一个不可分割的工作单位,要么全部提交,要么全部失败回滚
    • undo日志用来保障原子性
  • Consistency 一致性
    • 一致性指数据库对数据设立了一定的约束,事务开始前,数据是处于这种约束下的,事务完成后也要保证数据前后一致,即不违法约束和规则
  • Isolation 隔离性
    • 隔离性指事务之间不能相互影响,不能被其他事务干扰
  • Durability 持久性
    • 持久性指事务一旦提交,它对数据库的改变将是永久的
    • redo日志用来保障持久性

11.2 事务的状态

MySQL深入学习 --- 事务和三大日志详解_第1张图片

11.3 如何使用事务

3.1 显式事务

  1. STATRT TRANSACTIONBEGIN
    • START TRANSACTION 后能跟修饰符
      • READ ONLY:表示是一个只读事务 ,该事务内数据库操作只能读取数据,不能修改数据
      • READ WRITE:表示是一个读写事务 ,该事务内数据库操作既可以读也可以写
      • WITH CONSISTENT SNAPSHOT :启动一致性读
  2. 一系列事务中的操作(主要是DML,不含DDL),增删改查
  3. 提交事务COMMIT或者回滚ROLLBACK

3.2 隐式事务

MySQL中有个系统变量autocommit

这种自动提交功能,可以用以下两种方法关闭:

  • 显式的的使用 START TRANSACTION 或者 BEGIN语句开启一个事务。这样在本次事务提交或者回滚前会暂时关闭掉自动提交的功能
  • 把系统变量 autocommit的值设置为 OFFSET autocommit = OFF;

11.4 事务隔离级别

4.1 数据并发问题

  1. 脏写(Dirty Write)
    • 如果一个事务修改了另一个未提交事务修改过的数据,就意味着发生了脏写现象。脏写就是丢失修改,一个事务把另外一个事务修改的操作覆盖掉了
  2. 脏读(Dirty Read)
    • 如果一个事务读到了另一个未提交事务修改过的数据 ,就意味着发生了脏读现象。比如两个事务同时读到一个数据,其中一个事务要对这个数据进行修改,还没修改就被另一个事务读到了,这就造成了脏读。
    • 脏读需要在隔离级别为READ UNCOMMITTED 读未提交下才会出现,一般很少出现。
  3. 不可重复读(Unrepeatableread)
    • 如果一个事务修改了另一个提交事务读取的数据,就意味着发生了不可重复读现象。比如事务A先读到了一条数据,然后事务B对这条数据进行了修改,然后事务A再读这条数据就不是原来的值了,这就是不可重复读。
    • 不可重复读可能会在READ UNCOMMITTED 读未提交READ COMMITTED 读已提交下出现
    • 不可重复读的重点是修改
  4. 幻读(Phantom read)
    • 幻读和不可重复读类似。不同在于,幻读是第二次读可能多了几条记录或者少了几条记录
    • 幻读可能会在READ UNCOMMITTED 读未提交READ COMMITTED 读已提交REPEATABLE READ 可重复读下出现
    • 幻读的重点在于新增或者删除

4.2 事务隔离级别

SQL标准定义了以下四种隔离级别

  • READ UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读

  • READ COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生

  • REPEATABLE READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生

  • SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读

    • SERIALIZABLE主要用于InnoDB的分布式事务

隔离级别越低,事务请求的锁越少或者保持锁的时间就越短

InnoDB引擎默认支持的是REPEATABLE READ(可重复读)

与标准SQL不同,InnoDB使用了Next-Key Lock锁算法避免幻读的发生

所以,InnoDB引擎在默认REPEATABLE READ隔离级别下已经完全能保证事务的隔离性要求了

4.3 小结

MySQL深入学习 --- 事务和三大日志详解_第2张图片

十二、MySQL三大日志

MySQL日志中比较重要的三个日志分别为:

  • binlog:二进制日志,用来保证数据的一致性,数据备份,主备、主主、主从都需要binlog
  • redo log:重做日志,用来保证事务的持久性
  • undo log:回滚日志,用来保证事务的原子性

12.1 binlog

binlog(二进制日志)记录了对MySQL数据库 执行更改的所有操作 (但是不包括SELECT和SHOW这类操作,因为这类操作对数据没修过),二进制日志主要有以下几个作用:

  • 恢复 :某些数据的恢复需要二进制日志。
  • 复制 :原理和恢复类似,通过复制和执行二进制日志使两台远程的MySQL数据库进行实时同步(主从复制)
  • 审计 :用户可以通过二进制日志中的信息进行审计,判断是否有对数据库进行注入的攻击

1.1 配置文件参数

MySQL深入学习 --- 事务和三大日志详解_第3张图片

  • max_binlog_size: 指定单个二进制日志文件的最大值,如果超过该值就产生新的二进制日志文件,后缀名+1,并记录到 .index 文件

  • binlog_cache_size: InnoDB使用事务时,所有未提交的二进制日志会先被记录到一个缓存中,等待事务提交时直接将缓存中的二进制日志写入到二进制日志文件中。这个缓冲池大小有该参数控制,默认为32K

  • sync_binlog = [N]: 表示每写缓存多少次就同步到硬盘,N默认为0

    • N = 0,表示每次提交事务都只写入缓存,由系统自行判断什么时候刷盘到二进制日志文件中
      • 缺点:系统宕机时,缓存中所有的二进制日志丢失
    • N = 1,表示每次提交一个事务都执行刷盘,将缓存中的日志刷入二进制日志文件中
    • N > 1,表示每提交累计N个事务才进行刷盘,当出现IO瓶颈时,可以将该值设成一个较大的值
      • 缺点:系统宕机时,缓存中最近N个事务的二进制日志丢失
  • binlog_fomat: 这个参数影响了记录的二进制日志格式,十分重要

    • statement:日志记录内容是SQL语句原文
      • 问题:如果SQL使用了now()函数,获取系统时间,这个级别会导致与原来不一致
    • row:日志记录内容不仅包括SQL还包括操作的具体数据,row格式记录的内容看不到详细信息,要通过mysqlbinlog工具解析出来
      • 问题:这种方式需要更大的容量来记录,比较消耗IO资源,所有有了mixed这种折中的方式
    • mixed:这种方式是上述两种方式的混合,Mysql会判断SQL是否会导致数据不一致
      • 如果会就用row格式
      • 不会就用statement格式

12.2 redo日志

redo日志是InnoDB引擎独有的,使MySQL拥有崩溃恢复 的功能,从而保证数据库的持久性和完整性

2.1 为什么要用redo日志

InnoDB引擎是以页为单位关联存储空间的,增删改查本质上说就是访问页面。在真正访问页面之前,需要把磁盘中的页加载到内存中的Buffer Pool中才可以访问。

如果事务提交后,刚写完Buffer Pool,服务器就宕机了,这就导致内存中数据丢失,那么这个已提交的事务的修改也跟着丢失了,这是我们不能忍受的。

所以需要一些解决方案

  • 方案一:简单粗暴的方法,每次事务提交完成之前,把事务修改的所有页面刷新到磁盘
    • 问题:刷新一个完整的数据页太浪费IO了。同时一个事务可能有多个语句,一个语句可能修改多个页面。这就意味着Buffer Pool刷新到硬盘需要很多的随机IO,随机IO较顺序IO慢多了
  • 方案二:其实没必要每次提交事务时把全部页面刷新到硬盘,只需要把修改的内容记录一下就好,比如只记录:修改第100号页面中偏移量为1000处那个值从1改为2。

上述方案二便是redo日志的思想

redo日志优点:

  • 占用空间小。
  • 顺序IO。 执行事务时每执行一条语句就可能产生若干条redo日志,这些日志是按照产生顺序写入磁盘的,也就是顺序IO

2.2 redo 日志格式

MySQL深入学习 --- 事务和三大日志详解_第4张图片

  • redo_log_type: 重做日志类型
    • MySQL深入学习 --- 事务和三大日志详解_第5张图片
  • space: 表空间ID
  • page_no: 页的偏移量
  • redo log body: 根据重做日志类型不同会有不同的存储内容

2.3 Mini-Transaction

设计MySQL的大叔对底层页面进行一层原子访问的过程 称为一个Mini-Transaction (MTR)

一个MTR可以包含一组redo日志,在进行崩溃恢复时,需要把这一组 redo 日志作为一个不可分割的整体 来处理。

一个事务可以包含若干条语向,每一条语句又包含若干个 MTR,每一个 MTR 又可以包含着若干条 redo 日志。

MySQL深入学习 --- 事务和三大日志详解_第6张图片

2.4 redo日志写入的过程

2.4.1 redo log block 重做日志块

在InnoDB存储引擎中,重做日志都是以512字节存储的,这意味着重做日志缓存、重做日志文件都是以块(block)的方式进行保存的,称之为重做日志块(redo log block),每块大小512字节

MySQL深入学习 --- 事务和三大日志详解_第7张图片

真正的redo日志是存在496字节里的log block body里的

2.4.2 redo log buffer 重做日志缓冲区

和Buffer Pool同理,引入重做日志缓冲区也是为了解决磁盘速度过慢,写入redo日志时不能直接写到磁盘上,

在服务器启动时就向操作系统申请了一大片redo log buffer的连续内存空间 如图:

MySQL深入学习 --- 事务和三大日志详解_第8张图片

我们可以通过innodb_log_buffer_size来指定log buffer的大小,MySQL 5.7.22 默认为16MB

2.4.3 redo 日志写入log buffer

向log buffer写入是顺序写入的,全局变量buf_free保存偏移量,告诉mysql该往哪写

MySQL深入学习 --- 事务和三大日志详解_第9张图片

前面说了,每个mtr都会产生一组redo日志,这些redo日志是一个不可分割的组

所以并不是每生成一条redo日志就将其插入到log buffer,而是将每个mtr产生的日志暂存到一个地方;当该mtr结束的时候,再将这一组redo日志全部复制到log buffer中

MySQL深入学习 --- 事务和三大日志详解_第10张图片

2.5 redo 日志文件

2.5.1 redo 日志刷盘时机

InnoDB为redo log的刷盘策略提供了innodb_flush_log_at_trx_commit参数,它支持三种策略

  • 0 :设置为 0 的时候,表示每次事务提交时不进行刷盘操作

  • 1 :设置为 1 的时候,表示每次事务提交时都将进行刷盘操作**(默认值)**

  • 2 :设置为 2 的时候,表示每次事务提交时都只把 redo log buffer 内容写入 page cache(文件系统缓存)

Innodb后台有一个线程,大约以每秒一次的频率将log buffer中的redo日志刷新到磁盘

图示:

MySQL深入学习 --- 事务和三大日志详解_第11张图片

设置为0,提交事务的时候不会刷盘,每隔一秒由后台线程进行刷盘

MySQL深入学习 --- 事务和三大日志详解_第12张图片

设置为1,提交事务的时候会刷一次盘,同时每隔一秒后台线程也会刷盘

MySQL深入学习 --- 事务和三大日志详解_第13张图片

设置为2,提交事务的时候会刷到操作系统文件缓存中,同时每隔一秒后台线程也会刷盘

2.5.2 日志文件组

MySQL数据目录下默认有两个文件:ib_logfile0ib_logfile1,log buffer中的日志在默认情况下会刷到这两个文件中。如果对默认的不满意,可以通过下面几个启动选项调节:

  • innodb_log_group_home_dir:指定了redo日志文件的所在目录
  • innodb_log_file_size:指定每个redo日志文件的大小,MySQL5.7.22 默认48MB
  • innodb_log_files_in_group:指定redo日志文件的个数,默认为2,最大为100

MySQL深入学习 --- 事务和三大日志详解_第14张图片

磁盘上的redo日志文件是以组的方式出现的,在redo日志写入文件组时,从0号开始写,写满了就写1号,往下一直这样写,最后一个写满了就转到0号开始写

redo日志文件大小:innodb_log_file_size × innodb_log_files_in_group

2.5.3 checkpoint

MySQL深入学习 --- 事务和三大日志详解_第15张图片

如果 write pos 追上 checkpoint,表示日志文件组满了,这时候不能再写入新的 redo log记录,MySQL 得停下来,清空一些记录,把 checkpoint推进一下

2.5.4 崩溃恢复

在崩溃恢复过程中,从redo日志文件组中第一个文件的管理信息中(管理信息存在日志文件前2048个字节中,也就是前4个block)取出最近发生的那次checkpoint信息,然后从checkpoint_lsn在日志文件组中对应的偏移量开始,一直扫描日志文件中的block,直到某个block的LOG_BLOCK_HDR_DATA_LEN值不等于512为止。

在恢复过程中,使用哈希表可以加速恢复过程,并且会跳过已经刷新到磁盘的页面

12.3 undo日志

3.1 为什么需要undo日志

我们说过,事务需要保证原子性,但是偏偏有时候事务在执行到一半的时候会出现一些问题,比如:

  • 事务执行过程中突然断电
  • 程序员手动输入ROLLBACK语句结束事务执行

为了保证原子性,这时候就需要回滚来改回原来的样子。设计数据库的大叔把每一条记录的改动都记录一下,比如:

  • 在Insert时,至少把这条记录的主键记下来,回滚只需要把这个记录删掉就好了
  • 在Delete时,至少把这条记录中的内容都记下来,回滚再把这条记录插入到表里就好了
  • 在Update时,至少把这条记录更新的列的旧值记下来,回滚时再把这些列改回去旧值就行了

这些为了回滚而记录的东西叫做撤销日志(undo log)

3.2 事务id

在说事务id时,要回忆一下InnoDB记录行格式,如图:聚簇索引的记录除了保存完整的用户数据之外,还会自动添加名为trx_idroll_pointer的隐藏列

  • trx_id:这个就是保存对这条记录进行改动的语句所在事务的事务id
  • roll_pointer:这个用来指向原来的undo日志版本,在MVCC有很多的作用

MySQL深入学习 --- 事务和三大日志详解_第16张图片

MySQL深入学习 --- 事务和三大日志详解_第17张图片

如图,每生成一条undo日志,相关的记录的roll_pointer就会指向这个undo日志,如果该事务内有很多修改同一条记录的undo日志,就将其串成链表,最近的undo日志离记录更近

MySQL深入学习 --- 事务和三大日志详解_第18张图片

如图串成版本链,这个将会在MVCC上起到作用

3.2.1 分配事务id的时机

事务可以是只读事务,也可以是读写事务。如果某个事务在执行过程中对表执行了增删改操作,那么InnoDB引擎就会为它分配一个独一无二的事务id

  • 对于只读事务 ,只有在它第一次对某个用户创建的临时表执行增删改操作时才会为这个事务分配一个事务id,否则是不分配事务id的,即默认为0
  • 对于读写事务 ,只有它第一次对某个表(包括某个用户创建的临时表)执行增删改查时才会分配,否则是不分配的,默认为0

3.2.2 事务id怎么生成的

  • 服务器内存中维护一个全局变量,每当需要为某个事务分配事务id时,就会把这个值赋给它并且该变量自增1

  • 当这个变量值为256的倍数时,会进行刷盘,刷到系统表空间页号为5的页面中一个名为Max Trx ID的属性中

  • 系统下次启动的时候,读取这个Max Trx ID属性加载到内存并加上256赋给之前的全局变量,因为上次该全局变量的值可能大于磁盘中Max Trx ID属性的值,所以要再加上256

3.3 undo日志格式

在InnoDB引擎中,undo log分为

  • insert undo log: 记录insert操作产生的undo log
  • update undo log: 记录update和delete操作产生的undo log

3.3.1 insert undo log

insert 操作的记录只对事务本身可见,对其他事务不可见,故undo log可以在提交事务后直接删除,不需要进行purge操作

insert的回滚日志类型为:TRX_UNDO_INSERT_REC

MySQL深入学习 --- 事务和三大日志详解_第19张图片
  • next: 本条日志结束,下一条日志开始时的页面地址
  • type_cmpl: 记录本条日志的类型,也即是TRX_UNDO_INSERT_REC
  • undo no: 本条undo日志对应的编号
  • table id: 本条undo日志对应的记录所在表的table id
  • n_unique_index: 主键各个列占用的存储空间大小len,以及真实值col
  • start: 上一条undo日志结束,本条开始时的页面地址

3.3.2 update undo log

update undo log 可能需要提供MVCC机制,因此不能在事务提交的时候就删除,提交时放入undo log链表,等待purge线程进行最后的删除

MySQL深入学习 --- 事务和三大日志详解_第20张图片
  • type_cmpl: 类型,update undo log记录的内容更多,本身还有分类:
    • 12 TRX_UNDO_UPD_EXIST_REC 更新non-delete-mark 的记录
    • 13 TRX_UNDO_UPD_DEL_REC 将delete 的记录标记为not delete
    • 14 TRX_UNDO_DEL_MARK_REC 将记录标记为delete
  • info_bits: 记录头信息的前4比特的值
  • DATA_TRX_ID: 旧纪录的trx_id值
  • DATA_ROLL_PTR: 旧纪录的roll_pointer值,记录这个是为了串成版本链
  • update vector: 记录update操作导致发生改变的列,每个修改的列信息都要记录到undo log,对于不同的undo log类型,可能还需要记录对索引列所做的修改
1.Delete 操作对应的日志

MySQL深入学习 --- 事务和三大日志详解_第21张图片

举个例子,这是页面内记录一个时间段的初始状态,现在要执行一条DELETE语句

DELETE语句的操作要进行两个步骤

  • 阶段一: 将要删除的记录的deleted_flag标识位置为1,其他的不用修改,这个阶段称为:delete mark,这阶段undo日志类型为:TRX_UNDO_DEL_MARK_REC

    MySQL深入学习 --- 事务和三大日志详解_第22张图片

  • 阶段二: 当删除语句所在事务真正提交后,将该记录从正常记录链表上移除,放到垃圾链表中,此处PAGE_FREE指的是可重用空间,如果有新的记录插入,如果空间够用,直接覆盖这部分的记录

    MySQL深入学习 --- 事务和三大日志详解_第23张图片

2.Update 操作对应的日志

对于Update语句,InnoDB对更新主键和不更新主键两种情况有截然不同的的处理方法

2.1 不更新主键

这种方式类型为:TRX_UNDO_UPD_EXIST_REC

  • 就地更新: 如果更新后的列与更新前的列占用的空间一模一样大 ,就可以就地更新

  • 先删除旧纪录,再插入新纪录: 如果更新后的列与之前的空间有一点不一样,大了或小了都不行,就用这个方法,那么就需要将这条记录移除,即直接加入到垃圾链表中 ,然后新建一条新的记录插入到页面中

2.2 更新主键

在聚簇索引中,记录按照主键值大小串成单向链表,如果我们更新了某条记录的主键值,意味着该记录会改变位置

InnoDB在聚簇索引中分了两步进行处理

  1. 将旧纪录进行delete mark操作
    • 注意:和前面不更新主键操作不一样,这个是delete mark,在事务提交后再由专门的线程进行purge操作
  2. 根据更新后各列的值创建一条新纪录,插入到聚簇索引中

3.4 FIL_PAGE_ UNDO_LOG页面

表空间其实是许许多多的页面构成的,页面默认大小为16KB,而页面有很多类型,比如类型为FIL_PAGE_INDEX的页面用于存储聚簇索引以及二级索引,类型为FIL_PAGE_TYPE_FSP_HDR的页面用来存储表空间头部信息

FIL_PAGE_UNDO_LOG类型用来存储undo日志,简称undo日志页

每个undo日志页分为两大类:TRX UNDO INSERTTRX UNDO UPDATE,这两大类分别存insert和update undo log,两者不能混着存

每个页面有两个属性TRX_UNDO_PAGE_START表示页面从什么地方开始存日志,TRX_UNDO_PAGE_FREE表示当前页面可以从这个位置开始继续写

MySQL深入学习 --- 事务和三大日志详解_第24张图片

3.5 Undo页面链表

一个事务可能包含多个语句,一个语句可能包含多个undo日志,所以一个事务执行过程中可能产生很多undo日志,并且可能一个页面放不下,就要放到多个页面,这些页面串起来就是Undo页面链表

有两个链表对应两种Undo页面类型

MySQL深入学习 --- 事务和三大日志详解_第25张图片

同时,Innodb规定普通表和临时表记录的undo日志必须分开:所以一个事务在执行过程最多可以分配4个Undo页面链表

MySQL深入学习 --- 事务和三大日志详解_第26张图片

3.6 undo日志写入过程

6.1 段的概念

说到段要先说区,区(Extent)是为了更好管理表空间中的页而提出来的概念,对于16KB的页,连续64个页就是一个区,一个区就默认1MB,同时每256个区算一个区组。

我们每向表中插入一条记录,本质上就是向表的聚簇索引以及二级索引代表的B+树中插入数据,而B+树中每个页虽然通过双向链表连接,但是实际上物理位置可能离的很远,这时候对于传统机械硬盘来说,要重新定位磁头位置即产生了随机IO,这就会影响磁盘性能。所以,为了让页面链表相邻的页尽量相邻,引入了区的概念,一个区就是物理位置上连续的64页,这样就能消除很多的随机IO

但事情还没有结束,我们在使用B+树查询时如果不区分叶子节点和非叶子节点,统统放进申请到的区中,扫描效果就大打折扣了。所以,InnoDB设计者对B+树的叶子节点和非叶子节点进行区别的对待。即叶子节点有独立的区,非叶子结点也有独立的区,存放叶子节点的区的集合就算一个段,非叶子结点页同理。也就是说一个索引会生成两个段:一个叶子节点段一个非叶子节点段

6.2 回滚段undo log segment

设计InnoDB 的大叔规定,每一个Undo页面链表都对应着一个段,称为Undo Log Segment。也就是说,链表中的页面都是从这个段中申请的,所以他们在Undo页面链表的第一个页面(first undo page)中设计了一个名为Undo Log Segment Header的部分。

MySQL深入学习 --- 事务和三大日志详解_第27张图片
Undo Log Segment Header

Undo页面第一个页面比普通页面多了个Undo Log Segment Header,如图为其结构

MySQL深入学习 --- 事务和三大日志详解_第28张图片

  • TRX_UNDO_STATE:本Undo页面链表处于的状态
  • TRX_UNDO_LAST_LOG:本Undo页面链表中最后一个Undo Log Header位置
  • TRX_UNDO_FSEG_HEADER:本Undo页面链表对应的段的Segment Header信息
  • TRX_UNDO_PAGE_LIST:Undo页面链表基节点
Undo页面链表

MySQL深入学习 --- 事务和三大日志详解_第29张图片


回滚段概念

我们知道,一个事务在执行过程中最多可以分配4个Undo页面链表。在同一时刻,不同事务拥有的Undo页面链表是不一样的,系统在同一时刻其实可以存在许多个Undo页面链表。

为了更好地管理这些链表,设计InnoDB的大叔又设计了一个名为Rollback Segment Header 的页面。这个页面中存放了各个Undo页面链表的first undo page的页号,这些页号称为undo slot

MySQL深入学习 --- 事务和三大日志详解_第30张图片

如图为Rollback Segment Header页面,InnoDB规定,每一个Rollback Segment Header页面对应一个段,这个段称为回滚段

  • TRX_RSEG_MAX_SIZE:这个回滚段中管理的所有Undo页面链表中的Undo页面数量之和的最大值。

    • 换句话说,在这个回滚段中,所有Undo页面链表中的Undo页面数量之和不能超过TRX_RSEG_MAX_SIZE代表的值。该属性的值默认为无限大,也就是想创建多少个Undo页面都可以。
  • TRX_RSEG_HISTORY_SIZE: History链表占用的页面数量。

  • TRX_RSEG_HISTORY: History链表的基节点。

  • TRX_RSEG_FSEG_HEADER: 这个回滚段对应的10字节大小的Segment Header结构,通过它可以找到本回滚段对应的INODE Entry。

  • TRX_RSEG_UNDO_SLOTS: 各个Undo页面链表的first undo page的页号集合,也就是undo slot集合。

一个页号占用4字节,对于大小为16KB的页面来说,这个TRX_RSEG_UNDO_SLOTS部分共存储了1,024个undo slot,所以共需1,024×4= 4,096字节。

6.3 为事务分配Undo页面链表的过程

  1. 事务在执行过程中对普通表的记录进行首次改动之前,首先会分配一个回滚段
  2. 在分配到回滚段后,首先看一下这个回滚段的两个cached链表有没有已经缓存的undo slot。
    • insert操作就去insert undo cached链表查看
    • update操作就去update undo cached链表查看
    • 如果有缓存的undo slot,就把这个undo slot分配给该事务
  3. 如果没有缓存的undo slot可供分配,那么就要到Rollback Segment Header页面中找一个可用的undo slot分配给当前事务。
  4. 找到可用的undo slot后
    • 如果该undo slot是从cached链表中获取的,那么它对应的Undo Log Segment就已经分配了;
    • 否则需要重新分配一个Undo Log Segment,然后从该Undo Log Segment中申请一个页面作为Undo页面链表的first undo page,并把该页的页号填入获取的undo slot 中。
  5. 然后事务就可以把undo日志写入到上面申请的Undo页面链表中了。

3.7 undo日志在崩溃恢复的作用

服务器崩溃恢复的过程中,首先要按照redo日志将各个页面恢复到崩溃之前的状态,从而保证持久性

而redo日志中那些没有提交的事务可能也以及刷盘,那么这些未提交的事务修改过的页面在服务器重启时有可能被恢复了。

为了保证原子性,有必要在服务器重启时将这些未提交的事务回滚掉。那么,怎么找到这些未提交的事务呢?这个工作又落到了undo日志头上。
过程:

  • 我们可以通过系统表空间的第5号页面定位到128个回滚段的位置,在每一个回滚段的 1,024个undo slot中找到那些值不为FIL_NULLundo slot,每一个undo slot对应着一个Undo页面链表。

  • 然后从Undo页面链表第一个页面的Undo Segment Header中找到TRX_UNDO_STATE属性,该属性标识当前Undo页面链表所处的状态。

    • 如果该属性的值为TRX_UNDO_ACTIVE,则意味着有一个活跃的事务正在向这个Undo页面链表中写入undo日志。
  • 然后再在Undo Segment Header中找到TRX_UNDO_LAST_LOG属性,通过该属性可以找到本Undo页面链表最后一个Undo Log Header的位置。

  • 从该Undo Log Header中可以找到对应事务的事务id以及一些其他信息,则该事务id对应的事务就是未提交的事务。

  • 通过undo日志中记录的信息将该事务对页面所做的更改全部回滚掉,这样就保证了事务的原子性。


简单来说,就是找undo slot中的undo页面链表,找到之前状态是TRX_UNDO_ACTIVE的日志,然后找到其对应位置,和事务id及其他信息,将这些更改回滚掉

12.4 日志总结

  • InnoDB 引擎使用 redo log(重做日志) 保证事务的持久性

  • 使用 undo log(回滚日志) 来保证事务的原子性

  • 数据备份、主备、主主、主从都离不开**binlog,需要依靠binlog来同步数据,保证数据一致性**。

你可能感兴趣的:(mysql,mysql,学习,数据库)