MySQL-InnoDB-日志&事务


  • redo 日志
  • undo 日志
  • 事务流程
  • Redo & binlog & Undo 日志的关系

redo 日志

  • redo 日志(保证了事务的持久性):只是记录了一下事务对数据库做了哪些修改
    • redo 日志占用的空间非常小
    • redo 日志是顺序写入磁盘的
      • type:该条redo 日志的类型,redo 日志设计大约有 53 种不同的类型日志
      • space ID:表空间 ID
      • page number:页号
      • data:该条 redo 日志的具体内容
  • redo 日志的写入过程
    • 日志缓冲区 innodb_log_buffer_size 默认16MB
    • redo log block 512 字节:向log buffer 中写入redo 日志的过程是顺序的,也就是先往前边的block 中写,当该block 的空闲空间用完之后再往下一个 block 中写
    • redo 日志刷盘时机
      • log buffer 总容量的大约一半左右
      • 事务提交时
      • 后台有一个线程,大约每秒都会刷新一次 log buffer 中的 redo 日志到磁盘
      • 正常关闭服务器时
  • redo 日志文件组 ib_logfile0 和 ib_logfile1
    • innodb_log_file_size 每个 redo 日志文件的大小,默认 48MB
    • innodb_log_files_in_group 指定 redo 日志文件的个数,默认值为 2,最大值为100
    • 循环写入:在覆盖写之前,总是要保证对应的脏页已经刷到了磁盘
      • 在非常大的负载下,为避免错误的覆盖,InnoDB 会强制的 flush 脏页
  • redo 日志文件格式
    • 前 4 个 block 是用来存储一些管理信息
    • 往后是用来存储 log buffer 中的 block 镜像的
  • LSN
    • Log Sequence Number 日志序列号,简称 LSN,初始值 8704
      • redo 日志都有一个唯一的 LSN 值与其对应,LSN 值越小,说明 redo 日志产生的越早
      • 包括了写到log buffer而没有刷新到磁盘的日志
    • Log flushed up to (flushed_to_disk_lsn) 表示刷新到磁盘中的 redo 日志量的全局变量
    • Pages flushed up to:代表 flush 链表中被最早修改的那个页面对应的 oldest_modification 属性值
    • Last checkpoint at:当前系统的 checkpoint_lsn 值
  • innodb_flush_log_at_trx_commit 默认值1
    • 0:表示在事务提交时不立即向磁盘中同步redo 日志,交给后台线程做
    • 1:表示在事务提交时需要将 redo 日志同步到磁盘,可以保证事务的持久性
    • 2:表示在事务提交时需要将 redo 日志写到操作系统的缓冲区中,但并不需要保证将日志真正的刷新到磁盘

undo 日志

  • undo 日志(保证事务的原子性)
  • 事务id
    • 给事务分配id 的时机
      • 如果某个事务执行过程中对某个表执行了增、删、改操作,会给它分配一个独一无二的事务 id
      • 只读事务:对某个用户创建的临时表执行增、删、改操作时才会为这个事务分配一个事务 id
        • 在执行 SELECT 语句用到内部临时表时并不会为它分配事务id
      • 否则的话是不分配事务 id 的
    • 事务 id 生成机制:本质上就是一个数字,全局变量自增
      • 当这个变量的值为 256 的倍数时,就会将该变量的值刷新到系统表空间的页号为5 的页面中一个称之为 Max Trx ID 的属性处(8字节)
      • 下一次重新启动时,会将上边提到的 Max Trx ID 属性加载到内存中,将该值加上256 之后赋值给我们前边提到的全局变量
  • trx_id 隐藏列:聚簇索引的记录除了会保存完整的用户数据以外,而且还会自动添加名为 trx_id、roll_pointer 的隐藏列,如果用户没有在表中定义主键以及 UNIQUE 键,还会自动添加一个名为 row_id 的隐藏列
    • trx_id 列就是某个对这个聚簇索引记录做改动的语句所在的事务对应的事务id 而已(此处的改动可以是 INSERT、DELETE、UPDATE 操作)
    • roll_pointer 本质上就是一个指向记录对应的 undo 日志的一个指针
  • undo 日志的格式:一般每对一条记录做一次改动,就对应着一条 undo 日志
    • INSERT 操作对应的 undo 日志 TRX_UNDO_INSERT_REC
      • 记录 undo 日志时,只需要考虑向聚簇索引插入记录时的情况就好了,因为其实聚簇索引记录和二级索引记录是一一对应的,我们在回滚插入操作时,只需要知道这条记录的主键信息,然后根据主键信息做对应的删除操作,做删除操作时就会顺带着把所有二级索引中相应的记录也删除掉
    • DELETE 操作对应的 undo 日志 TRX_UNDO_DEL_MARK_REC
      • 被删除的记录会根据记录头信息中的 next_record 属性组成一个链表(垃圾链表),Page Header 部分称之为PAGE_FREE 的属性指向由被删除记录组成的垃圾链表中的头节点
        1. 将记录的delete_mask 标识位设置为1,这个阶段称之为delete mark
        1. 当该删除语句所在的事务提交之后,会有专门的线程后来真正的把记录删除掉,这个阶段称之为 purge
        • 所谓真正的删除就是把该记录从正常记录链表中移除,并且加入到垃圾链表中
        • 然后还要调整一些页面的其他信息
      • 在删除语句所在的事务提交之前,只会经历阶段一,也就是 delete mark 阶段,所以只需考虑对删除操作的阶段一做的影响进行回滚
      • 版本链:在对一条记录进行 delete mark 操作前,需要把该记录的旧的 trx_id 和roll_pointer 隐藏列的值都给记到对应的undo 日志中来,可以通过 undo 日志的old roll_pointer 找到记录在修改之前对应的 undo 日志
    • UPDATE 操作对应的 undo 日志
      • 不更新主键的情况 TRX_UNDO_UPD_EXIST_REC
        • 占用的存储空间不发生变化:就地更新(in-place update)
        • 占用的存储空间发生变化:先删除掉旧记录,再插入新记录
          • 并不是 delete mark 操作,而是真正的删除掉,也就是把这条记录从正常记录链表中移除并加入到垃圾链表中(同步执行),并且修改页面中相应的统计信息
          • 如果新创建的记录占用的存储空间大小不超过旧记录占用的空间,那么可以直接重用被加入到垃圾链表中的旧记录所占用的存储空间
      • 更新主键的情况 TRX_UNDO_DEL_MARK_REC & TRX_UNDO_INSERT_REC (会记录 2 条 undo 日志)
        • 将旧记录进行 delete mark 操作(MVCC)
        • 创建一条新记录

事务流程

  • 事务的流程:分为事务的执行流程和事务恢复流程
  • 事务执行:事务主要主要是通过 Redo Log 和 Undo Log 实现的
      • 只有当前事务相关的所有 Redo Log 刷盘成功,事务才算提交成功
  • 事务恢复
      • 如果事务提交之前崩溃或者宕机,先使用Redo Log 恢复数据,然后使用Undo Log 回滚数据
      • 如果事务提交之后崩溃或者宕机,会使用Redo Log 恢复数据
    • 恢复机制:根据 redo 日志中的各种LSN 值,来确定恢复的起点和终点
      • 将redo 日志中的数据,以哈希表的形式,将一个页面下的放到哈希表的一个槽中。之后就可以遍历哈希表,所以可以一次性将一个页面修复好

Redo & binlog & Undo 日志的关系

  • Redo 日志和 binlog 日志的关系
    • binlog 是用作人工恢复数据
    • redo log 是 InnoDB 引擎特有的,binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用
    • redo log 是物理日志,记录的是“在某个数据页上做了什么修改”,恢的速度更快;binlog 是逻辑日志,记录的是这个语句的原始逻辑
    • redo log 是“循环写”的日志文件,redo log 只会记录未刷盘的日志,已经刷入磁盘的数据都会从 redo log 这个有限大小的日志文件里删除。binlog 是追加日志,保存的是全量的日志
    • 当数据库 crash 后,想要恢复未刷盘但已经写入 redo log 和binlog 的数据到内存时,binlog 是无法恢复的。虽然 binlog 拥有全量的日志,但没有一个标志让 innoDB 判断哪些数据已经入表(写入磁盘),哪些数据还没有
    • redo log 不一样,只要刷入磁盘的数据,都会从 redo log 中抹掉,数据库重启后,直接把 redo log 中的数据都恢复至内存就可以了
  • Redo 日志和 Undo 日志的关系
    • undo log 日志的完整性和可靠性需要 redo log 日志来保证
      • undo log 的写入也会伴随着redo log 的产生,因为undo log也需要持久化的保护
    • 数据库崩溃需要先做 redo log 数据恢复,然后做undo log 回滚(未提交的事务)
  • 同时写 Redo 和 Binlog 怎么保持一致:两阶段事务 2PC(准备阶段;提交阶段)
    • 当事务提交时 InnoDB 存储引擎进行 prepare 操作
    • MySQL 上层会将数据库、数据表和数据表中的数据的更新操作写入 BinLog
    • InnoDB 存储引擎将事务日志写入 Redo Log 文件中

文章作者: 钱不寒
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 钱不寒 !
  目录